diff --git a/.github/workflows/autotest.yml b/.github/workflows/autotest.yml index bfdfe49d0..dff85a1ed 100644 --- a/.github/workflows/autotest.yml +++ b/.github/workflows/autotest.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] architecture: ['x86', 'x64'] npsupport: ['with npsupport', 'without npsupport'] steps: @@ -43,7 +43,7 @@ jobs: strategy: matrix: os: [windows-latest, windows-2019] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] architecture: ['x86', 'x64'] steps: - uses: actions/checkout@v4 diff --git a/comtypes/_meta.py b/comtypes/_meta.py index 5f74109a9..7c1fa24ee 100644 --- a/comtypes/_meta.py +++ b/comtypes/_meta.py @@ -61,6 +61,18 @@ def __new__(cls, name, bases, namespace): if "_reg_clsid_" in namespace: clsid = namespace["_reg_clsid_"] comtypes.com_coclass_registry[str(clsid)] = self + + # `_coclass_pointer_meta` is a subclass inherited from `_coclass_meta`. + # In other words, when the `__new__` method of this metaclass is called, an + # instance of `_coclass_pointer_meta` might be created and assigned to `self`. + if isinstance(self, _coclass_pointer_meta): + # `self` is the `_coclass_pointer_meta` type or a `POINTER(coclass)` type. + # Prevent creating/registering a pointer to a pointer (to a pointer...), + # or specifying the metaclass type instance in the `bases` parameter when + # instantiating it, which would lead to infinite recursion. + # Depending on a version or revision of Python, this may be essential. + return self + PTR = _coclass_pointer_meta( f"POINTER({self.__name__})", (self, c_void_p), diff --git a/comtypes/_post_coinit/unknwn.py b/comtypes/_post_coinit/unknwn.py index 825073b1b..7380f25b2 100644 --- a/comtypes/_post_coinit/unknwn.py +++ b/comtypes/_post_coinit/unknwn.py @@ -76,6 +76,31 @@ def __new__(cls, name, bases, namespace): if dispmethods is not None: self._disp_methods_ = dispmethods + # ` _compointer_meta` is a subclass inherited from `_cominterface_meta`. + # `_compointer_base` uses `_compointer_meta` as its metaclass. + # In other words, when the `__new__` method of this metaclass is called, + # `_compointer_base` type or an subclass of it might be created and assigned + # to `self`. + if bases == (c_void_p,): + # `self` is the `_compointer_base` type. + # On some versions of Python, `_compointer_base` is not yet available in + # the accessible namespace at this point in its initialization, and + # referencing it could raise a `NameError`. + # This metaclass is intended to be used only as a base class for defining + # the `_compointer_meta` or as the metaclass for the `IUnknown`, so the + # situation where the `bases` parameter is `(c_void_p,)` is limited to when + # the `_compointer_meta` is specified as the metaclass of the + # `_compointer_base`. + # Prevent specifying the metaclass type instance in the `bases` parameter + # when instantiating it, as this would lead to infinite recursion. + return self + if issubclass(self, _compointer_base): + # `self` is a `POINTER(interface)` type. + # Prevent creating/registering a pointer to a pointer (to a pointer...), + # which would lead to infinite recursion. + # Depending on a version or revision of Python, this may be essential. + return self + # If we sublass a COM interface, for example: # # class IDispatch(IUnknown): diff --git a/comtypes/safearray.py b/comtypes/safearray.py index 7bcae99ff..70e7c3abe 100644 --- a/comtypes/safearray.py +++ b/comtypes/safearray.py @@ -81,9 +81,7 @@ def _make_safearray_type(itemtype): ) meta = type(_safearray.tagSAFEARRAY) - sa_type = meta.__new__( - meta, "SAFEARRAY_%s" % itemtype.__name__, (_safearray.tagSAFEARRAY,), {} - ) + sa_type = meta(f"SAFEARRAY_{itemtype.__name__}", (_safearray.tagSAFEARRAY,), {}) try: vartype = _ctype_to_vartype[itemtype]