From 7df371fca46d3ba2c39e704d9ea74cffa67c39c3 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 2 Nov 2024 18:14:09 +0900 Subject: [PATCH 1/3] Change methods to top level functions. --- .../_cominterface_meta_patcher.py | 282 +++++++++--------- 1 file changed, 140 insertions(+), 142 deletions(-) diff --git a/comtypes/_post_coinit/_cominterface_meta_patcher.py b/comtypes/_post_coinit/_cominterface_meta_patcher.py index 8e7ef7b8..5a8f4b68 100644 --- a/comtypes/_post_coinit/_cominterface_meta_patcher.py +++ b/comtypes/_post_coinit/_cominterface_meta_patcher.py @@ -9,63 +9,59 @@ _all_slice = slice(None, None, None) -class _cominterface_meta(type): - @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. +def case_insensitive(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) + + +def reference_fix(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. # - # 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 + # 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 _make_specials(self): # This call installs methods that forward the Python protocols @@ -78,89 +74,91 @@ def has_name(name): return name.lower() in self.__map_case__ return hasattr(self, name) - # XXX These special methods should be generated by the code generator. - def _patch_sized(self): - @patcher.Patch(self) - class _(object): - def __len__(self): - "Return the the 'self.Count' property." - return self.Count - - def _patch_callable_and_subscriptable(self): - @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: # Unknown error - 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: # Unknown error - raise - except TypeError: - msg = "%r object does not support item assignment" - raise TypeError(msg % type(self)) - - def _patch_iterator(self): - @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) + +def sized(itf: Type) -> None: + @patcher.Patch(itf) + class _(object): + def __len__(self): + "Return the the 'self.Count' property." + return self.Count + + +def callable_and_subscriptable(itf: Type) -> None: + @patcher.Patch(itf) + 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: # Unknown error + 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: # Unknown error + raise + except TypeError: + msg = "%r object does not support item assignment" + raise TypeError(msg % type(self)) + + +def iterator(itf: Type) -> None: + @patcher.Patch(itf) + 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) From a5d8bc1469be83bb66565a32cb606b3bec54ad5e Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 2 Nov 2024 18:14:10 +0900 Subject: [PATCH 2/3] Update the import section. --- comtypes/_post_coinit/_cominterface_meta_patcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/comtypes/_post_coinit/_cominterface_meta_patcher.py b/comtypes/_post_coinit/_cominterface_meta_patcher.py index 5a8f4b68..0009fa9a 100644 --- a/comtypes/_post_coinit/_cominterface_meta_patcher.py +++ b/comtypes/_post_coinit/_cominterface_meta_patcher.py @@ -1,6 +1,5 @@ -from _ctypes import COMError - import types +from _ctypes import COMError from typing import Type from comtypes import patcher From 20496d62d08d10f70e5bd049c49f772ede406fbe Mon Sep 17 00:00:00 2001 From: junkmd Date: Sat, 2 Nov 2024 18:19:48 +0900 Subject: [PATCH 3/3] Remove redundant comments. --- comtypes/_post_coinit/_cominterface_meta_patcher.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/comtypes/_post_coinit/_cominterface_meta_patcher.py b/comtypes/_post_coinit/_cominterface_meta_patcher.py index 0009fa9a..d70e73a0 100644 --- a/comtypes/_post_coinit/_cominterface_meta_patcher.py +++ b/comtypes/_post_coinit/_cominterface_meta_patcher.py @@ -26,7 +26,6 @@ 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""" @@ -40,12 +39,10 @@ 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