Skip to content

Commit

Permalink
Separate patch processing within _com_interface_meta into a module. (
Browse files Browse the repository at this point in the history
…#646)

* Restore comments.

* Remove the unused remnant.

* Use top level functions instead of methods.

* Update import orderings.
  • Loading branch information
junkmd authored Nov 2, 2024
1 parent c9cb523 commit d80810b
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 166 deletions.
19 changes: 6 additions & 13 deletions comtypes/_post_coinit/_cominterface_meta_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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"):
Expand Down
161 changes: 8 additions & 153 deletions comtypes/_post_coinit/unknwn.py
Original file line number Diff line number Diff line change
@@ -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__)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d80810b

Please sign in to comment.