Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate patch processing within _com_interface_meta into a module. #646

Merged
merged 4 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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