diff --git a/comtypes/_post_coinit/_cominterface_meta_patcher.py b/comtypes/_post_coinit/_cominterface_meta_patcher.py index d70e73a0..78e064e6 100644 --- a/comtypes/_post_coinit/_cominterface_meta_patcher.py +++ b/comtypes/_post_coinit/_cominterface_meta_patcher.py @@ -26,6 +26,7 @@ def __getattr__(self, name): # 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""" @@ -39,10 +40,12 @@ 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 @@ -59,17 +62,6 @@ def __setitem__(self, index, value): CopyComPointer(value, self) # type: ignore - def _make_specials(self): - # This call installs methods that forward the Python protocols - # to COM protocols. - - def has_name(name): - # Determine whether a property or method named 'name' - # exists - if self._case_insensitive_: - return name.lower() in self.__map_case__ - return hasattr(self, name) - def sized(itf: Type) -> None: @patcher.Patch(itf) @@ -108,7 +100,7 @@ def __getitem__(self, index): (hresult, text, details) = err.args if hresult == -2147352565: # DISP_E_BADINDEX raise IndexError("invalid index") - else: # Unknown error + else: raise # Note that result may be NULL COM pointer. There is no way @@ -127,7 +119,7 @@ def __setitem__(self, index, value): (hresult, text, details) = err.args if hresult == -2147352565: # DISP_E_BADINDEX raise IndexError("invalid index") - else: # Unknown error + else: raise except TypeError: msg = "%r object does not support item assignment" @@ -149,6 +141,7 @@ def __iter__(self): enum = self._NewEnum if isinstance(enum, types.MethodType): # _NewEnum should be a propget property, with dispid -4. + # # Sometimes, however, it is a method. enum = enum() if hasattr(enum, "Next"): diff --git a/comtypes/_post_coinit/unknwn.py b/comtypes/_post_coinit/unknwn.py index 7380f25b..3b345489 100644 --- a/comtypes/_post_coinit/unknwn.py +++ b/comtypes/_post_coinit/unknwn.py @@ -1,24 +1,19 @@ # https://learn.microsoft.com/en-us/windows/win32/api/unknwn/ -from ctypes import byref, c_ulong, c_void_p, HRESULT, POINTER -from _ctypes import COMError - import logging import sys -import types +from ctypes import HRESULT, POINTER, byref, c_ulong, c_void_p from typing import ClassVar, TYPE_CHECKING, TypeVar from typing import Optional from typing import List, Type -from comtypes import GUID, patcher, _ole32_nohresult, com_interface_registry +from comtypes import GUID, _ole32_nohresult, com_interface_registry from comtypes._idl_stuff import STDMETHOD from comtypes._memberspec import ComMemberGenerator, DispMemberGenerator from comtypes._memberspec import _ComMemberSpec, _DispMemberSpec +from comtypes._post_coinit import _cominterface_meta_patcher as _meta_patch from comtypes._py_instance_method import instancemethod - -_all_slice = slice(None, None, None) - logger = logging.getLogger(__name__) @@ -136,68 +131,11 @@ def __new__(cls, name, bases, namespace): _pointer_type_cache[self] = p if self._case_insensitive_: - self._patch_case_insensitive_to_ptr_type(p) - self._patch_reference_fix_to_ptrptr_type(POINTER(p)) # type: ignore + _meta_patch.case_insensitive(p) + _meta_patch.reference_fix(POINTER(p)) # type: ignore return self - @staticmethod - def _patch_case_insensitive_to_ptr_type(p: Type) -> None: - @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) # Should we use exception-chaining? - 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 - ) - - @staticmethod - def _patch_reference_fix_to_ptrptr_type(pp: Type) -> None: - @patcher.Patch(pp) - 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(pp, self).__setitem__(index, value) # type: ignore - return - from _ctypes import CopyComPointer - - CopyComPointer(value, self) # type: ignore - def __setattr__(self, name, value): if name == "_methods_": # XXX I'm no longer sure why the code generator generates @@ -225,94 +163,11 @@ def has_name(name): # XXX These special methods should be generated by the code generator. if has_name("Count"): - - @patcher.Patch(self) - class _(object): - def __len__(self): - "Return the the 'self.Count' property." - return self.Count - + _meta_patch.sized(self) if has_name("Item"): - - @patcher.Patch(self) - class _(object): - # 'Item' is the 'default' value. Make it available by - # calling the instance (Not sure this makes sense, but - # win32com does this also). - def __call__(self, *args, **kw): - "Return 'self.Item(*args, **kw)'" - return self.Item(*args, **kw) - - # does this make sense? It seems that all standard typelibs I've - # seen so far that support .Item also support ._NewEnum - @patcher.no_replace - def __getitem__(self, index): - "Return 'self.Item(index)'" - # Handle tuples and all-slice - if isinstance(index, tuple): - args = index - elif index == _all_slice: - args = () - else: - args = (index,) - - try: - result = self.Item(*args) - except COMError as err: - (hresult, text, details) = err.args - if hresult == -2147352565: # DISP_E_BADINDEX - raise IndexError("invalid index") - else: - raise - - # Note that result may be NULL COM pointer. There is no way - # to interpret this properly, so it is returned as-is. - - # Hm, should we call __ctypes_from_outparam__ on the - # result? - return result - - @patcher.no_replace - def __setitem__(self, index, value): - "Attempt 'self.Item[index] = value'" - try: - self.Item[index] = value - except COMError as err: - (hresult, text, details) = err.args - if hresult == -2147352565: # DISP_E_BADINDEX - raise IndexError("invalid index") - else: - raise - except TypeError: - msg = "%r object does not support item assignment" - raise TypeError(msg % type(self)) - + _meta_patch.callable_and_subscriptable(self) if has_name("_NewEnum"): - - @patcher.Patch(self) - class _(object): - def __iter__(self): - "Return an iterator over the _NewEnum collection." - # This method returns a pointer to _some_ _NewEnum interface. - # It relies on the fact that the code generator creates next() - # methods for them automatically. - # - # Better would maybe to return an object that - # implements the Python iterator protocol, and - # forwards the calls to the COM interface. - enum = self._NewEnum - if isinstance(enum, types.MethodType): - # _NewEnum should be a propget property, with dispid -4. - # - # Sometimes, however, it is a method. - enum = enum() - if hasattr(enum, "Next"): - return enum - # _NewEnum returns an IUnknown pointer, QueryInterface() it to - # IEnumVARIANT - from comtypes.automation import IEnumVARIANT - - return enum.QueryInterface(IEnumVARIANT) + _meta_patch.iterator(self) def _make_case_insensitive(self): # The __map_case__ dictionary maps lower case names to the