Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/autofmt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.9
- name: Install ruff
run: pip install ruff==0.6.9
- name: Check format
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/autotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
os: [windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
architecture: ['x86', 'x64']
support: ['with 3rd parties', 'without 3rd parties']
steps:
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
strategy:
matrix:
os: [windows-2025, windows-2022]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
architecture: ['x86', 'x64']
steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

`comtypes` allows you to define, call, and implement custom and dispatch-based COM interfaces in pure Python.

`comtypes` requires Windows and Python 3.8 or later.
`comtypes` requires Windows and Python 3.9 or later.
- Version [1.4.12](https://pypi.org/project/comtypes/1.4.12/) is the last version to support Python 3.8.
- Version <= [1.4.7](https://pypi.org/project/comtypes/1.4.7/) does not work with Python 3.13 as reported in [GH-618](https://github.com/enthought/comtypes/issues/618). Version [1.4.8](https://pypi.org/project/comtypes/1.4.8/) can work with Python 3.13.
- Version [1.4.6](https://pypi.org/project/comtypes/1.4.6/) is the last version to support Python 3.7.
- Version [1.2.1](https://pypi.org/project/comtypes/1.2.1/) is the last version to support Python 2.7 and 3.3–3.6.
- `comtypes` does not work with Python 3.8.1 as reported in [GH-202](https://github.com/enthought/comtypes/issues/202). This bug has been fixed in Python >= 3.8.2.
- Certain `comtypes` functions may not work correctly in Python 3.8 and 3.9 as reported in [GH-212](https://github.com/enthought/comtypes/issues/212). This bug has been fixed in Python >= 3.10.10 and >= 3.11.2.

## Installation
Expand Down
10 changes: 7 additions & 3 deletions comtypes/_meta.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# comtypes._meta helper module
import sys
from ctypes import POINTER, c_void_p, cast

import comtypes
Expand Down Expand Up @@ -74,17 +75,20 @@ def __new__(cls, name, bases, namespace):
# Depending on a version or revision of Python, this may be essential.
return self

PTR = _coclass_pointer_meta(
p = _coclass_pointer_meta(
f"POINTER({self.__name__})",
(self, c_void_p),
{
"__ctypes_from_outparam__": _wrap_coclass,
"from_param": classmethod(_coclass_from_param),
},
)
from ctypes import _pointer_type_cache # type: ignore
if sys.version_info >= (3, 14):
self.__pointer_type__ = p
else:
from ctypes import _pointer_type_cache # type: ignore

_pointer_type_cache[self] = PTR
_pointer_type_cache[self] = p

return self

Expand Down
8 changes: 6 additions & 2 deletions comtypes/_post_coinit/unknwn.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# https://learn.microsoft.com/en-us/windows/win32/api/unknwn/

import logging
import sys
from ctypes import HRESULT, POINTER, byref, c_ulong, c_void_p
from typing import TYPE_CHECKING, Any, ClassVar, List, Optional, Type, TypeVar

Expand Down Expand Up @@ -126,9 +127,12 @@ def __new__(cls, name, bases, namespace):
{"__com_interface__": self, "_needs_com_addref_": None},
)

from ctypes import _pointer_type_cache # type: ignore
if sys.version_info >= (3, 14):
self.__pointer_type__ = p
else:
from ctypes import _pointer_type_cache # type: ignore

_pointer_type_cache[self] = p
_pointer_type_cache[self] = p

if self._case_insensitive_:
_meta_patch.case_insensitive(p)
Expand Down
8 changes: 8 additions & 0 deletions comtypes/test/test_comserver.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import doctest
import sys
import unittest
from ctypes import pointer
from typing import Any
Expand Down Expand Up @@ -140,6 +141,13 @@ class TestInproc_win32com(BaseServerTest, unittest.TestCase):
def create_object(self):
return Dispatch("TestComServerLib.TestComServer")

if sys.version_info >= (3, 14):

@unittest.skip("Fails occasionally with a memory leak on INPROC.")
def test_eval(self):
# This test sometimes leaks memory when run as an in-process server.
pass

# These tests make no sense with win32com, override to disable them:
@unittest.skip("This test make no sense with win32com.")
def test_get_typeinfo(self):
Expand Down
3 changes: 1 addition & 2 deletions comtypes/test/test_excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ def test(self):

@unittest.skipIf(IMPORT_FAILED, "This depends on Excel.")
@unittest.skipIf(
sys.version_info[:2] == (3, 8)
or sys.version_info[:2] == (3, 9)
sys.version_info[:2] == (3, 9)
or (sys.version_info[:2] == (3, 10) and sys.version_info < (3, 10, 10))
or (sys.version_info[:2] == (3, 11) and sys.version_info < (3, 11, 2)),
f"This fails in {PY_VER}. See https://github.com/enthought/comtypes/issues/212",
Expand Down
76 changes: 63 additions & 13 deletions comtypes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
and cast_field(struct, fieldname, fieldtype).
"""

import sys
from ctypes import (
POINTER,
Structure,
Expand All @@ -15,6 +16,7 @@
c_float,
c_int,
c_long,
c_longdouble,
c_longlong,
c_short,
c_void_p,
Expand All @@ -38,17 +40,61 @@ def _calc_offset():
# The definition of PyCArgObject in C code (that is the type of
# object that a byref() call returns):
class PyCArgObject(Structure):
if sys.version_info >= (3, 14):
# While C compilers automatically determine appropriate
# alignment based on field data types, `ctypes` requires
# explicit control over memory layout.
#
# `_pack_ = 8` ensures 8-byte alignment for fields.
#
# This works on both 32-bit and 64-bit systems:
# - On 64-bit systems, this matches the natural alignment
# for pointers.
# - On 32-bit systems, this is more strict than necessary
# (4-byte would be enough), but still produces the
# correct memory layout with proper padding.
#
# With `_pack_`, `ctypes` will automatically add padding
# here to ensure proper alignment of the `value` field
# after the `tag` and after the `size`.
_pack_ = 8
else:
# No special packing needed for Python 3.13 and earlier
# because the default alignment works fine for the legacy
# structure.
pass

class value(Union):
_fields_ = [
("c", c_char),
("h", c_short),
("i", c_int),
("l", c_long),
("q", c_longlong),
("d", c_double),
("f", c_float),
("p", c_void_p),
]
if sys.version_info >= (3, 14):
# In Python 3.14, the tagPyCArgObject structure was
# modified to better support complex types.
_fields_ = [
("c", c_char),
("b", c_char),
("h", c_short),
("i", c_int),
("l", c_long),
("q", c_longlong),
("g", c_longdouble),
("d", c_double),
("f", c_float),
("p", c_void_p),
# arrays for real and imaginary of complex
("D", c_double * 2),
("F", c_float * 2),
("G", c_longdouble * 2),
]
else:
_fields_ = [
("c", c_char),
("h", c_short),
("i", c_int),
("l", c_long),
("q", c_longlong),
("d", c_double),
("f", c_float),
("p", c_void_p),
]

#
# Thanks to Lenard Lindstrom for this tip:
Expand All @@ -66,9 +112,13 @@ class value(Union):
_anonymous_ = ["value"]

# additional checks to make sure that everything works as expected

if sizeof(PyCArgObject) != type(byref(c_int())).__basicsize__:
raise RuntimeError("sizeof(PyCArgObject) invalid")
expected_size = type(byref(c_int())).__basicsize__
actual_size = sizeof(PyCArgObject)
if actual_size != expected_size:
raise RuntimeError(
f"sizeof(PyCArgObject) mismatch: expected {expected_size}, "
f"got {actual_size}."
)

obj = c_int()
ref = byref(obj)
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ classifiers =
Topic :: Software Development :: Libraries :: Python Modules

[options]
python_requires = >=3.8
python_requires = >=3.9

packages =
comtypes
Expand Down