From ab44f937e6190bc95f84732e53fe031ae8b9ab99 Mon Sep 17 00:00:00 2001 From: junkmd Date: Mon, 23 Sep 2024 12:21:54 +0900 Subject: [PATCH] Trial and error: For Python 3.13. --- comtypes/_meta.py | 70 ++++++--- comtypes/_post_coinit/unknwn.py | 259 ++++++++++++++++++++++---------- 2 files changed, 227 insertions(+), 102 deletions(-) diff --git a/comtypes/_meta.py b/comtypes/_meta.py index 4476c424..4ab42690 100644 --- a/comtypes/_meta.py +++ b/comtypes/_meta.py @@ -1,5 +1,7 @@ # comtypes._meta helper module from ctypes import POINTER, c_void_p, cast +import sys + import comtypes ################################################################ @@ -42,27 +44,55 @@ class _coclass_meta(type): # POINTER(...) type gets a __ctypes_from_outparam__ method which # will QueryInterface for the default interface: the first one on # the coclass' _com_interfaces_ list. - def __new__(cls, name, bases, namespace): - klass = type.__new__(cls, name, bases, namespace) - if bases == (object,): + + if sys.version_info >= (3, 13): + + def __new__(cls, name, bases, namespace): + klass = type.__new__(cls, name, bases, namespace) + return klass + + def __init__(self, name, bases, namespace): + if bases == (object,): + return self + # XXX We should insist that a _reg_clsid_ is present. + if "_reg_clsid_" in namespace: + clsid = namespace["_reg_clsid_"] + comtypes.com_coclass_registry[str(clsid)] = self + PTR = _coclass_pointer_meta( + "POINTER(%s)" % 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 + + _pointer_type_cache[self] = PTR + + else: + + def __new__(cls, name, bases, namespace): + klass = type.__new__(cls, name, bases, namespace) + if bases == (object,): + return klass + # XXX We should insist that a _reg_clsid_ is present. + if "_reg_clsid_" in namespace: + clsid = namespace["_reg_clsid_"] + comtypes.com_coclass_registry[str(clsid)] = klass + PTR = _coclass_pointer_meta( + "POINTER(%s)" % klass.__name__, + (klass, c_void_p), + { + "__ctypes_from_outparam__": _wrap_coclass, + "from_param": classmethod(_coclass_from_param), + }, + ) + from ctypes import _pointer_type_cache # type: ignore + + _pointer_type_cache[klass] = PTR + return klass - # XXX We should insist that a _reg_clsid_ is present. - if "_reg_clsid_" in namespace: - clsid = namespace["_reg_clsid_"] - comtypes.com_coclass_registry[str(clsid)] = klass - PTR = _coclass_pointer_meta( - "POINTER(%s)" % klass.__name__, - (klass, c_void_p), - { - "__ctypes_from_outparam__": _wrap_coclass, - "from_param": classmethod(_coclass_from_param), - }, - ) - from ctypes import _pointer_type_cache - - _pointer_type_cache[klass] = PTR - - return klass # will not work if we change the order of the two base classes! diff --git a/comtypes/_post_coinit/unknwn.py b/comtypes/_post_coinit/unknwn.py index 046d1bc2..b9cb158c 100644 --- a/comtypes/_post_coinit/unknwn.py +++ b/comtypes/_post_coinit/unknwn.py @@ -66,96 +66,191 @@ class _cominterface_meta(type): _com_shutting_down = False # Creates also a POINTER type for the newly created class. - def __new__(cls, name, bases, namespace): - methods = namespace.pop("_methods_", None) - dispmethods = namespace.pop("_disp_methods_", None) - new_cls = type.__new__(cls, name, bases, namespace) + if sys.version_info >= (3, 13): - if methods is not None: - new_cls._methods_ = methods - if dispmethods is not None: - new_cls._disp_methods_ = dispmethods + def __new__(cls, name, bases, namespace): + methods = namespace.pop("_methods_", None) + dispmethods = namespace.pop("_disp_methods_", None) + new_cls = type.__new__(cls, name, bases, namespace) - # If we sublass a COM interface, for example: - # - # class IDispatch(IUnknown): - # .... - # - # then we need to make sure that POINTER(IDispatch) is a - # subclass of POINTER(IUnknown) because of the way ctypes - # typechecks work. - if bases == (object,): - _ptr_bases = (new_cls, _compointer_base) - else: - _ptr_bases = (new_cls, POINTER(bases[0])) + if methods is not None: + new_cls._methods_ = methods + if dispmethods is not None: + new_cls._disp_methods_ = dispmethods + return new_cls - # The interface 'new_cls' is used as a mixin. - p = type(_compointer_base)( - "POINTER(%s)" % new_cls.__name__, - _ptr_bases, - {"__com_interface__": new_cls, "_needs_com_addref_": None}, - ) + def __init__(self, name, bases, namespace): + # If we sublass a COM interface, for example: + # + # class IDispatch(IUnknown): + # .... + # + # then we need to make sure that POINTER(IDispatch) is a + # subclass of POINTER(IUnknown) because of the way ctypes + # typechecks work. + if bases == (object,): + _ptr_bases = (self, _compointer_base) + else: + _ptr_bases = (self, POINTER(bases[0])) - from ctypes import _pointer_type_cache + # The interface 'new_cls' is used as a mixin. + p = type(_compointer_base)( + "POINTER(%s)" % self.__name__, + _ptr_bases, + {"__com_interface__": self, "_needs_com_addref_": None}, + ) - _pointer_type_cache[new_cls] = p + from ctypes import _pointer_type_cache # type: ignore - if new_cls._case_insensitive_: + _pointer_type_cache[self] = p - @patcher.Patch(p) - class CaseInsensitive(object): - # case insensitive attributes for COM methods and properties - def __getattr__(self, name): - """Implement case insensitive access to methods and properties""" - try: - fixed_name = self.__map_case__[name.lower()] - except KeyError: + if self._case_insensitive_: + + @patcher.Patch(p) + class CaseInsensitive(object): + # case insensitive attributes for COM methods and properties + def __getattr__(self, name): + """Implement case insensitive access to methods and properties""" + try: + fixed_name = self.__map_case__[name.lower()] + except KeyError: + raise AttributeError(name) + if fixed_name != name: # prevent unbounded recursion + return getattr(self, fixed_name) raise AttributeError(name) - if fixed_name != name: # prevent unbounded recursion - return getattr(self, fixed_name) - raise AttributeError(name) - - # __setattr__ is pretty heavy-weight, because it is called for - # EVERY attribute assignment. Settings a non-com attribute - # through this function takes 8.6 usec, while without this - # function it takes 0.7 sec - 12 times slower. - # - # How much faster would this be if implemented in C? - def __setattr__(self, name, value): - """Implement case insensitive access to methods and properties""" - object.__setattr__( - self, self.__map_case__.get(name.lower(), name), value - ) - - @patcher.Patch(POINTER(p)) - class ReferenceFix(object): - def __setitem__(self, index, value): - # We override the __setitem__ method of the - # POINTER(POINTER(interface)) type, so that the COM - # reference count is managed correctly. - # - # This is so that we can implement COM methods that have to - # return COM pointers more easily and consistent. Instead of - # using CopyComPointer in the method implementation, we can - # simply do: - # - # def GetTypeInfo(self, this, ..., pptinfo): - # if not pptinfo: return E_POINTER - # pptinfo[0] = a_com_interface_pointer - # return S_OK - if index != 0: - # CopyComPointer, which is in _ctypes, does only - # handle an index of 0. This code does what - # CopyComPointer should do if index != 0. - if bool(value): - value.AddRef() - super(POINTER(p), self).__setitem__(index, value) - return - from _ctypes import CopyComPointer - - CopyComPointer(value, self) - - return new_cls + + # __setattr__ is pretty heavy-weight, because it is called for + # EVERY attribute assignment. Settings a non-com attribute + # through this function takes 8.6 usec, while without this + # function it takes 0.7 sec - 12 times slower. + # + # How much faster would this be if implemented in C? + def __setattr__(self, name, value): + """Implement case insensitive access to methods and properties""" + object.__setattr__( + self, self.__map_case__.get(name.lower(), name), value + ) + + @patcher.Patch(POINTER(p)) + class ReferenceFix(object): + def __setitem__(self, index, value): + # We override the __setitem__ method of the + # POINTER(POINTER(interface)) type, so that the COM + # reference count is managed correctly. + # + # This is so that we can implement COM methods that have to + # return COM pointers more easily and consistent. Instead of + # using CopyComPointer in the method implementation, we can + # simply do: + # + # def GetTypeInfo(self, this, ..., pptinfo): + # if not pptinfo: return E_POINTER + # pptinfo[0] = a_com_interface_pointer + # return S_OK + if index != 0: + # CopyComPointer, which is in _ctypes, does only + # handle an index of 0. This code does what + # CopyComPointer should do if index != 0. + if bool(value): + value.AddRef() + super(POINTER(p), self).__setitem__(index, value) + return + from _ctypes import CopyComPointer + + CopyComPointer(value, self) + + else: + + def __new__(cls, name, bases, namespace): + methods = namespace.pop("_methods_", None) + dispmethods = namespace.pop("_disp_methods_", None) + new_cls = type.__new__(cls, name, bases, namespace) + + if methods is not None: + new_cls._methods_ = methods + if dispmethods is not None: + new_cls._disp_methods_ = dispmethods + + # If we sublass a COM interface, for example: + # + # class IDispatch(IUnknown): + # .... + # + # then we need to make sure that POINTER(IDispatch) is a + # subclass of POINTER(IUnknown) because of the way ctypes + # typechecks work. + if bases == (object,): + _ptr_bases = (new_cls, _compointer_base) + else: + _ptr_bases = (new_cls, POINTER(bases[0])) + + # The interface 'new_cls' is used as a mixin. + p = type(_compointer_base)( + "POINTER(%s)" % new_cls.__name__, + _ptr_bases, + {"__com_interface__": new_cls, "_needs_com_addref_": None}, + ) + + from ctypes import _pointer_type_cache # type: ignore + + _pointer_type_cache[new_cls] = p + + if new_cls._case_insensitive_: + + @patcher.Patch(p) + class CaseInsensitive(object): + # case insensitive attributes for COM methods and properties + def __getattr__(self, name): + """Implement case insensitive access to methods and properties""" + try: + fixed_name = self.__map_case__[name.lower()] + except KeyError: + raise AttributeError(name) + if fixed_name != name: # prevent unbounded recursion + return getattr(self, fixed_name) + raise AttributeError(name) + + # __setattr__ is pretty heavy-weight, because it is called for + # EVERY attribute assignment. Settings a non-com attribute + # through this function takes 8.6 usec, while without this + # function it takes 0.7 sec - 12 times slower. + # + # How much faster would this be if implemented in C? + def __setattr__(self, name, value): + """Implement case insensitive access to methods and properties""" + object.__setattr__( + self, self.__map_case__.get(name.lower(), name), value + ) + + @patcher.Patch(POINTER(p)) + class ReferenceFix(object): + def __setitem__(self, index, value): + # We override the __setitem__ method of the + # POINTER(POINTER(interface)) type, so that the COM + # reference count is managed correctly. + # + # This is so that we can implement COM methods that have to + # return COM pointers more easily and consistent. Instead of + # using CopyComPointer in the method implementation, we can + # simply do: + # + # def GetTypeInfo(self, this, ..., pptinfo): + # if not pptinfo: return E_POINTER + # pptinfo[0] = a_com_interface_pointer + # return S_OK + if index != 0: + # CopyComPointer, which is in _ctypes, does only + # handle an index of 0. This code does what + # CopyComPointer should do if index != 0. + if bool(value): + value.AddRef() + super(POINTER(p), self).__setitem__(index, value) + return + from _ctypes import CopyComPointer + + CopyComPointer(value, self) + + return new_cls def __setattr__(self, name, value): if name == "_methods_":