From 01ec70df21057b18f1c4a1c3f59666916218a817 Mon Sep 17 00:00:00 2001 From: Jun Komoda <45822440+junkmd@users.noreply.github.com> Date: Sat, 21 Dec 2024 15:14:24 +0900 Subject: [PATCH] Separate `_vtbl` from `_comobject`. (#713) * Replace `Union` with `_UnionT` in `_vtbl`. * Add `create_vtbl_mapping` to `_vtbl`. * Add `COMObject` to `hints`. * Add `create_dispimpl` to `_vtbl`. * Replace defining stuff with importing stuff from `_vtbl`. * Replace `Union` with `_UnionT` in `_comobject`. * Remove unused imports from `_comobject`. * Improve `tool.ruff.lint.per-file-ignores`. * Add comments. * Add comments. --- comtypes/_comobject.py | 414 +---------------------------------------- comtypes/_vtbl.py | 244 ++++++++++++------------ comtypes/hints.pyi | 2 +- pyproject.toml | 2 +- 4 files changed, 132 insertions(+), 530 deletions(-) diff --git a/comtypes/_comobject.py b/comtypes/_comobject.py index 47f92134..d68699f5 100644 --- a/comtypes/_comobject.py +++ b/comtypes/_comobject.py @@ -1,11 +1,8 @@ import logging -import os import queue -import sys from _ctypes import COMError, CopyComPointer from ctypes import ( POINTER, - WINFUNCTYPE, FormatError, Structure, byref, @@ -27,16 +24,15 @@ Tuple, Type, TypeVar, - Union, ) +from typing import Union as _UnionT -from comtypes import GUID, IPersist, IUnknown, ReturnHRESULT, instancemethod -from comtypes._memberspec import _encode_idl -from comtypes.errorinfo import ISupportErrorInfo, ReportError, ReportException +from comtypes import GUID, IPersist, IUnknown +from comtypes._vtbl import _MethodFinder, create_dispimpl, create_vtbl_mapping +from comtypes.errorinfo import ISupportErrorInfo from comtypes.hresult import ( DISP_E_BADINDEX, DISP_E_MEMBERNOTFOUND, - E_FAIL, E_INVALIDARG, E_NOINTERFACE, E_NOTIMPL, @@ -47,15 +43,12 @@ from comtypes.typeinfo import IProvideClassInfo, IProvideClassInfo2, ITypeInfo if TYPE_CHECKING: - from ctypes import _CArgObject, _FuncPointer, _Pointer + from ctypes import _CArgObject, _Pointer from comtypes import hints # type: ignore - from comtypes._memberspec import _ArgSpecElmType, _DispMemberSpec, _ParamFlagType logger = logging.getLogger(__name__) _debug = logger.debug -_warning = logger.warning -_error = logger.error ################################################################ # COM object implementation @@ -66,297 +59,6 @@ DISPATCH_PROPERTYPUT = 4 DISPATCH_PROPERTYPUTREF = 8 - -class E_NotImplemented(Exception): - """COM method is not implemented""" - - -def HRESULT_FROM_WIN32(errcode: Optional[int]) -> int: - "Convert a Windows error code into a HRESULT value." - if errcode is None: - return 0x80000000 - if errcode & 0x80000000: - return errcode - return (errcode & 0xFFFF) | 0x80070000 - - -def winerror(exc: Exception) -> int: - """Return the windows error code from a WindowsError or COMError - instance.""" - if isinstance(exc, COMError): - return exc.hresult - elif isinstance(exc, WindowsError): - code = exc.winerror - if isinstance(code, int): - return code - # Sometimes, a WindowsError instance has no error code. An access - # violation raised by ctypes has only text, for example. In this - # cases we return a generic error code. - return E_FAIL - raise TypeError( - f"Expected comtypes.COMERROR or WindowsError instance, got {type(exc).__name__}" - ) - - -def _do_implement(interface_name: str, method_name: str) -> Callable[..., int]: - def _not_implemented(*args): - """Return E_NOTIMPL because the method is not implemented.""" - _debug("unimplemented method %s_%s called", interface_name, method_name) - return E_NOTIMPL - - return _not_implemented - - -def catch_errors( - obj: "COMObject", - mth: Callable[..., Any], - paramflags: Optional[Tuple["_ParamFlagType", ...]], - interface: Type[IUnknown], - mthname: str, -) -> Callable[..., Any]: - clsid = getattr(obj, "_reg_clsid_", None) - - def call_with_this(*args, **kw): - try: - result = mth(*args, **kw) - except ReturnHRESULT as err: - (hresult, text) = err.args - return ReportError(text, iid=interface._iid_, clsid=clsid, hresult=hresult) - except (COMError, WindowsError) as details: - _error( - "Exception in %s.%s implementation:", - interface.__name__, - mthname, - exc_info=True, - ) - return HRESULT_FROM_WIN32(winerror(details)) - except E_NotImplemented: - _warning("Unimplemented method %s.%s called", interface.__name__, mthname) - return E_NOTIMPL - except: - _error( - "Exception in %s.%s implementation:", - interface.__name__, - mthname, - exc_info=True, - ) - return ReportException(E_FAIL, interface._iid_, clsid=clsid) - if result is None: - return S_OK - return result - - if paramflags is None: - has_outargs = False - else: - has_outargs = bool([x[0] for x in paramflags if x[0] & 2]) - call_with_this.has_outargs = has_outargs - return call_with_this - - -################################################################ - - -def hack( - inst: "COMObject", - mth: Callable[..., Any], - paramflags: Optional[Tuple["_ParamFlagType", ...]], - interface: Type[IUnknown], - mthname: str, -) -> Callable[..., Any]: - if paramflags is None: - return catch_errors(inst, mth, paramflags, interface, mthname) - code = mth.__code__ - if code.co_varnames[1:2] == ("this",): - return catch_errors(inst, mth, paramflags, interface, mthname) - dirflags = [f[0] for f in paramflags] - # An argument is an input arg either if flags are NOT set in the - # idl file, or if the flags contain 'in'. In other words, the - # direction flag is either exactly '0' or has the '1' bit set: - # Output arguments have flag '2' - - args_out_idx = [] - args_in_idx = [] - for i, a in enumerate(dirflags): - if a & 2: - args_out_idx.append(i) - if a & 1 or a == 0: - args_in_idx.append(i) - args_out = len(args_out_idx) - - ## XXX Remove this: - # if args_in != code.co_argcount - 1: - # return catch_errors(inst, mth, interface, mthname) - - clsid = getattr(inst, "_reg_clsid_", None) - - def call_without_this(this, *args): - # Method implementations could check for and return E_POINTER - # themselves. Or an error will be raised when - # 'outargs[i][0] = value' is executed. - # for a in outargs: - # if not a: - # return E_POINTER - - # make argument list for handler by index array built above - inargs = [] - for a in args_in_idx: - inargs.append(args[a]) - try: - result = mth(*inargs) - if args_out == 1: - args[args_out_idx[0]][0] = result - elif args_out != 0: - if len(result) != args_out: - msg = f"Method should have returned a {args_out}-tuple" - raise ValueError(msg) - for i, value in enumerate(result): - args[args_out_idx[i]][0] = value - except ReturnHRESULT as err: - (hresult, text) = err.args - return ReportError(text, iid=interface._iid_, clsid=clsid, hresult=hresult) - except COMError as err: - (hr, text, details) = err.args - _error( - "Exception in %s.%s implementation:", - interface.__name__, - mthname, - exc_info=True, - ) - try: - descr, source, helpfile, helpcontext, progid = details - except (ValueError, TypeError): - msg = str(details) - else: - msg = f"{source}: {descr}" - hr = HRESULT_FROM_WIN32(hr) - return ReportError(msg, iid=interface._iid_, clsid=clsid, hresult=hr) - except WindowsError as details: - _error( - "Exception in %s.%s implementation:", - interface.__name__, - mthname, - exc_info=True, - ) - hr = HRESULT_FROM_WIN32(winerror(details)) - return ReportException(hr, interface._iid_, clsid=clsid) - except E_NotImplemented: - _warning("Unimplemented method %s.%s called", interface.__name__, mthname) - return E_NOTIMPL - except: - _error( - "Exception in %s.%s implementation:", - interface.__name__, - mthname, - exc_info=True, - ) - return ReportException(E_FAIL, interface._iid_, clsid=clsid) - return S_OK - - if args_out: - call_without_this.has_outargs = True - return call_without_this - - -class _MethodFinder(object): - def __init__(self, inst: "COMObject") -> None: - self.inst = inst - # map lower case names to names with correct spelling. - self.names = dict([(n.lower(), n) for n in dir(inst)]) - - def get_impl( - self, - interface: Type[IUnknown], - mthname: str, - paramflags: Optional[Tuple["_ParamFlagType", ...]], - idlflags: Tuple[Union[str, int], ...], - ) -> Callable[..., Any]: - mth = self.find_impl(interface, mthname, paramflags, idlflags) - if mth is None: - return _do_implement(interface.__name__, mthname) - return hack(self.inst, mth, paramflags, interface, mthname) - - def find_method(self, fq_name: str, mthname: str) -> Callable[..., Any]: - # Try to find a method, first with the fully qualified name - # ('IUnknown_QueryInterface'), if that fails try the simple - # name ('QueryInterface') - try: - return getattr(self.inst, fq_name) - except AttributeError: - pass - return getattr(self.inst, mthname) - - def find_impl( - self, - interface: Type[IUnknown], - mthname: str, - paramflags: Optional[Tuple["_ParamFlagType", ...]], - idlflags: Tuple[Union[str, int], ...], - ) -> Optional[Callable[..., Any]]: - fq_name = f"{interface.__name__}_{mthname}" - if interface._case_insensitive_: - # simple name, like 'QueryInterface' - mthname = self.names.get(mthname.lower(), mthname) - # qualified name, like 'IUnknown_QueryInterface' - fq_name = self.names.get(fq_name.lower(), fq_name) - - try: - return self.find_method(fq_name, mthname) - except AttributeError: - pass - propname = mthname[5:] # strip the '_get_' or '_set' prefix - if interface._case_insensitive_: - propname = self.names.get(propname.lower(), propname) - # propput and propget is done with 'normal' attribute access, - # but only for COM properties that do not take additional - # arguments: - - if "propget" in idlflags and len(paramflags) == 1: - return self.getter(propname) - if "propput" in idlflags and len(paramflags) == 1: - return self.setter(propname) - _debug("%r: %s.%s not implemented", self.inst, interface.__name__, mthname) - return None - - def setter(self, propname: str) -> Callable[[Any], Any]: - # - def set(self, value): - try: - # XXX this may not be correct is the object implements - # _get_PropName but not _set_PropName - setattr(self, propname, value) - except AttributeError: - raise E_NotImplemented() - - return instancemethod(set, self.inst, type(self.inst)) - - def getter(self, propname: str) -> Callable[[], Any]: - def get(self): - try: - return getattr(self, propname) - except AttributeError: - raise E_NotImplemented() - - return instancemethod(get, self.inst, type(self.inst)) - - -def _create_vtbl_type( - fields: Tuple[Tuple[str, Type["_FuncPointer"]], ...], itf: Type[IUnknown] -) -> Type[Structure]: - try: - return _vtbl_types[fields] - except KeyError: - - class Vtbl(Structure): - _fields_ = fields - - Vtbl.__name__ = f"Vtbl_{itf.__name__}" - _vtbl_types[fields] = Vtbl - return Vtbl - - -# Ugh. Another type cache to avoid leaking types. -_vtbl_types: Dict[Tuple[Tuple[str, Type["_FuncPointer"]], ...], Type[Structure]] = {} - ################################################################ try: @@ -518,110 +220,12 @@ def __prepare_comobject(self) -> None: self.__make_interface_pointer(itf) def __make_interface_pointer(self, itf: Type[IUnknown]) -> None: - methods: List[Callable[..., Any]] = [] # method implementations - fields: List[Tuple[str, Type["_FuncPointer"]]] = [] # virtual function table - iids: List[GUID] = [] # interface identifiers. - # iterate over interface inheritance in reverse order to build the - # virtual function table, and leave out the 'object' base class. finder = self._get_method_finder_(itf) - for interface in itf.__mro__[-2::-1]: - iids.append(interface._iid_) - for m in interface._methods_: - restype, mthname, argtypes, paramflags, idlflags, helptext = m - proto = WINFUNCTYPE(restype, c_void_p, *argtypes) - fields.append((mthname, proto)) - mth = finder.get_impl(interface, mthname, paramflags, idlflags) - methods.append(proto(mth)) - Vtbl = _create_vtbl_type(tuple(fields), itf) - vtbl = Vtbl(*methods) + iids, vtbl = create_vtbl_mapping(itf, finder) for iid in iids: self._com_pointers_[iid] = pointer(pointer(vtbl)) if hasattr(itf, "_disp_methods_"): - self._dispimpl_ = {} - for m in itf._disp_methods_: - ################# - # What we have: - # - # restypes is a ctypes type or None - # argspec is seq. of (['in'], paramtype, paramname) tuples (or - # lists?) - ################# - # What we need: - # - # idlflags must contain 'propget', 'propset' and so on: - # Must be constructed by converting disptype - # - # paramflags must be a sequence - # of (F_IN|F_OUT|F_RETVAL, paramname[, default-value]) tuples - # - # comtypes has this function which helps: - # def _encode_idl(names): - # # convert to F_xxx and sum up "in", "out", - # # "retval" values found in _PARAMFLAGS, ignoring - # # other stuff. - # return sum([_PARAMFLAGS.get(n, 0) for n in names]) - ################# - - if m.what == "DISPMETHOD": - self.__make_dispmthentry(itf, finder, m) - elif m.what == "DISPPROPERTY": - self.__make_disppropentry(itf, finder, m) - - def __make_dispmthentry( - self, itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" - ) -> None: - _, mthname, idlflags, restype, argspec = m - if "propget" in idlflags: - invkind = DISPATCH_PROPERTYGET - mthname = f"_get_{mthname}" - elif "propput" in idlflags: - invkind = DISPATCH_PROPERTYPUT - mthname = f"_set_{mthname}" - elif "propputref" in idlflags: - invkind = DISPATCH_PROPERTYPUTREF - mthname = f"_setref_{mthname}" - else: - invkind = DISPATCH_METHOD - if restype: - argspec = argspec + ((["out"], restype, ""),) - self.__make_dispentry(finder, itf, mthname, idlflags, argspec, invkind) - - def __make_disppropentry( - self, itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" - ) -> None: - _, mthname, idlflags, restype, argspec = m - # DISPPROPERTY have implicit "out" - if restype: - argspec += ((["out"], restype, ""),) - self.__make_dispentry( - finder, itf, f"_get_{mthname}", idlflags, argspec, DISPATCH_PROPERTYGET - ) - if not "readonly" in idlflags: - self.__make_dispentry( - finder, itf, f"_set_{mthname}", idlflags, argspec, DISPATCH_PROPERTYPUT - ) - # Add DISPATCH_PROPERTYPUTREF also? - - def __make_dispentry( - self, - finder: _MethodFinder, - interface: Type[IUnknown], - mthname: str, - idlflags: Tuple[Union[str, int], ...], - argspec: Tuple["_ArgSpecElmType", ...], - invkind: int, - ) -> None: - # We build a _dispmap_ entry now that maps invkind and dispid to - # implementations that the finder finds; IDispatch_Invoke will later call it. - paramflags = [((_encode_idl(x[0]), x[1]) + tuple(x[3:])) for x in argspec] - # XXX can the dispid be at a different index? Check codegenerator. - dispid = idlflags[0] - impl = finder.get_impl(interface, mthname, paramflags, idlflags) - self._dispimpl_[(dispid, invkind)] = impl # type: ignore - # invkind is really a set of flags; we allow both DISPATCH_METHOD and - # DISPATCH_PROPERTYGET (win32com uses this, maybe other languages too?) - if invkind in (DISPATCH_METHOD, DISPATCH_PROPERTYGET): - self._dispimpl_[(dispid, DISPATCH_METHOD | DISPATCH_PROPERTYGET)] = impl + self._dispimpl_ = create_dispimpl(itf, finder) def _get_method_finder_(self, itf: Type[IUnknown]) -> _MethodFinder: # This method can be overridden to customize how methods are found. @@ -629,7 +233,7 @@ def _get_method_finder_(self, itf: Type[IUnknown]) -> _MethodFinder: ################################################################ # LocalServer / InprocServer stuff - __server__: Union[None, InprocServer, LocalServer] = None + __server__: _UnionT[None, InprocServer, LocalServer] = None @staticmethod def __run_inprocserver__() -> None: @@ -714,7 +318,7 @@ def IUnknown_QueryInterface( self, this: Any, riid: "_Pointer[GUID]", - ppvObj: Union[c_void_p, "_CArgObject"], + ppvObj: _UnionT[c_void_p, "_CArgObject"], _debug=_debug, ) -> int: # XXX This is probably too slow. diff --git a/comtypes/_vtbl.py b/comtypes/_vtbl.py index 7e233a4b..f3550573 100644 --- a/comtypes/_vtbl.py +++ b/comtypes/_vtbl.py @@ -1,18 +1,19 @@ import logging from _ctypes import COMError -from ctypes import WINFUNCTYPE, Structure, c_void_p, pointer +from ctypes import WINFUNCTYPE, Structure, c_void_p from typing import ( TYPE_CHECKING, Any, Callable, - ClassVar, Dict, + Iterator, List, Optional, + Sequence, Tuple, Type, - Union, ) +from typing import Union as _UnionT from comtypes import GUID, IUnknown, ReturnHRESULT, instancemethod from comtypes._memberspec import _encode_idl @@ -20,7 +21,7 @@ from comtypes.hresult import E_FAIL, E_NOTIMPL, S_OK if TYPE_CHECKING: - from ctypes import _FuncPointer, _Pointer + from ctypes import _FuncPointer from comtypes import hints # type: ignore from comtypes._memberspec import _ArgSpecElmType, _DispMemberSpec, _ParamFlagType @@ -81,7 +82,7 @@ def _not_implemented(*args): def catch_errors( - obj: "COMObject", + obj: "hints.COMObject", mth: Callable[..., Any], paramflags: Optional[Tuple["_ParamFlagType", ...]], interface: Type[IUnknown], @@ -130,7 +131,7 @@ def call_with_this(*args, **kw): def hack( - inst: "COMObject", + inst: "hints.COMObject", mth: Callable[..., Any], paramflags: Optional[Tuple["_ParamFlagType", ...]], interface: Type[IUnknown], @@ -231,7 +232,7 @@ def call_without_this(this, *args): class _MethodFinder(object): - def __init__(self, inst: "COMObject") -> None: + def __init__(self, inst: "hints.COMObject") -> None: self.inst = inst # map lower case names to names with correct spelling. self.names = dict([(n.lower(), n) for n in dir(inst)]) @@ -241,7 +242,7 @@ def get_impl( interface: Type[IUnknown], mthname: str, paramflags: Optional[Tuple["_ParamFlagType", ...]], - idlflags: Tuple[Union[str, int], ...], + idlflags: Tuple[_UnionT[str, int], ...], ) -> Callable[..., Any]: mth = self.find_impl(interface, mthname, paramflags, idlflags) if mth is None: @@ -263,7 +264,7 @@ def find_impl( interface: Type[IUnknown], mthname: str, paramflags: Optional[Tuple["_ParamFlagType", ...]], - idlflags: Tuple[Union[str, int], ...], + idlflags: Tuple[_UnionT[str, int], ...], ) -> Optional[Callable[..., Any]]: fq_name = f"{interface.__name__}_{mthname}" if interface._case_insensitive_: @@ -333,118 +334,115 @@ class Vtbl(Structure): ################################################################ -class COMObject(object): - _com_interfaces_: ClassVar[List[Type[IUnknown]]] - _outgoing_interfaces_: ClassVar[List[Type["hints.IDispatch"]]] - _instances_: ClassVar[Dict["COMObject", None]] = {} - _reg_clsid_: ClassVar[GUID] - _reg_typelib_: ClassVar[Tuple[str, int, int]] - __typelib: "hints.ITypeLib" - _com_pointers_: Dict[GUID, "_Pointer[_Pointer[Structure]]"] - _dispimpl_: Dict[Tuple[int, int], Callable[..., Any]] - - def __make_interface_pointer(self, itf: Type[IUnknown]) -> None: - methods: List[Callable[..., Any]] = [] # method implementations - fields: List[Tuple[str, Type["_FuncPointer"]]] = [] # virtual function table - iids: List[GUID] = [] # interface identifiers. - # iterate over interface inheritance in reverse order to build the - # virtual function table, and leave out the 'object' base class. - finder = self._get_method_finder_(itf) - for interface in itf.__mro__[-2::-1]: - iids.append(interface._iid_) - for m in interface._methods_: - restype, mthname, argtypes, paramflags, idlflags, helptext = m - proto = WINFUNCTYPE(restype, c_void_p, *argtypes) - fields.append((mthname, proto)) - mth = finder.get_impl(interface, mthname, paramflags, idlflags) - methods.append(proto(mth)) - Vtbl = _create_vtbl_type(tuple(fields), itf) - vtbl = Vtbl(*methods) - for iid in iids: - self._com_pointers_[iid] = pointer(pointer(vtbl)) - if hasattr(itf, "_disp_methods_"): - self._dispimpl_ = {} - for m in itf._disp_methods_: - ################# - # What we have: - # - # restypes is a ctypes type or None - # argspec is seq. of (['in'], paramtype, paramname) tuples (or - # lists?) - ################# - # What we need: - # - # idlflags must contain 'propget', 'propset' and so on: - # Must be constructed by converting disptype - # - # paramflags must be a sequence - # of (F_IN|F_OUT|F_RETVAL, paramname[, default-value]) tuples - # - # comtypes has this function which helps: - # def _encode_idl(names): - # # convert to F_xxx and sum up "in", "out", - # # "retval" values found in _PARAMFLAGS, ignoring - # # other stuff. - # return sum([_PARAMFLAGS.get(n, 0) for n in names]) - ################# - - if m.what == "DISPMETHOD": - self.__make_dispmthentry(itf, finder, m) - elif m.what == "DISPPROPERTY": - self.__make_disppropentry(itf, finder, m) - - def __make_dispmthentry( - self, itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" - ) -> None: - _, mthname, idlflags, restype, argspec = m - if "propget" in idlflags: - invkind = DISPATCH_PROPERTYGET - mthname = f"_get_{mthname}" - elif "propput" in idlflags: - invkind = DISPATCH_PROPERTYPUT - mthname = f"_set_{mthname}" - elif "propputref" in idlflags: - invkind = DISPATCH_PROPERTYPUTREF - mthname = f"_setref_{mthname}" - else: - invkind = DISPATCH_METHOD - if restype: - argspec = argspec + ((["out"], restype, ""),) - self.__make_dispentry(finder, itf, mthname, idlflags, argspec, invkind) - - def __make_disppropentry( - self, itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" - ) -> None: - _, mthname, idlflags, restype, argspec = m - # DISPPROPERTY have implicit "out" - if restype: - argspec += ((["out"], restype, ""),) - self.__make_dispentry( - finder, itf, f"_get_{mthname}", idlflags, argspec, DISPATCH_PROPERTYGET - ) - if not "readonly" in idlflags: - self.__make_dispentry( - finder, itf, f"_set_{mthname}", idlflags, argspec, DISPATCH_PROPERTYPUT - ) - # Add DISPATCH_PROPERTYPUTREF also? +def create_vtbl_mapping( + itf: Type[IUnknown], finder: _MethodFinder +) -> Tuple[Sequence[GUID], Structure]: + methods: List[Callable[..., Any]] = [] # method implementations + fields: List[Tuple[str, Type["_FuncPointer"]]] = [] # virtual function table + iids: List[GUID] = [] # interface identifiers. + # iterate over interface inheritance in reverse order to build the + # virtual function table, and leave out the 'object' base class. + for interface in itf.__mro__[-2::-1]: + iids.append(interface._iid_) + for m in interface._methods_: + restype, mthname, argtypes, paramflags, idlflags, helptext = m + proto = WINFUNCTYPE(restype, c_void_p, *argtypes) + fields.append((mthname, proto)) + mth = finder.get_impl(interface, mthname, paramflags, idlflags) + methods.append(proto(mth)) + Vtbl = _create_vtbl_type(tuple(fields), itf) + vtbl = Vtbl(*methods) + return (iids, vtbl) + + +def create_dispimpl( + itf: Type[IUnknown], finder: _MethodFinder +) -> Dict[Tuple[int, int], Callable[..., Any]]: + dispimpl: Dict[Tuple[int, int], Callable[..., Any]] = {} + for m in itf._disp_methods_: + ################# + # What we have: + # + # restypes is a ctypes type or None + # argspec is seq. of (['in'], paramtype, paramname) tuples (or + # lists?) + ################# + # What we need: + # + # idlflags must contain 'propget', 'propset' and so on: + # Must be constructed by converting disptype + # + # paramflags must be a sequence + # of (F_IN|F_OUT|F_RETVAL, paramname[, default-value]) tuples + # + # comtypes has this function which helps: + # def _encode_idl(names): + # # convert to F_xxx and sum up "in", "out", + # # "retval" values found in _PARAMFLAGS, ignoring + # # other stuff. + # return sum([_PARAMFLAGS.get(n, 0) for n in names]) + ################# + + if m.what == "DISPMETHOD": + dispimpl.update(_make_dispmthentry(itf, finder, m)) + elif m.what == "DISPPROPERTY": + dispimpl.update(_make_disppropentry(itf, finder, m)) + return dispimpl + + +def _make_dispmthentry( + itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" +) -> Iterator[Tuple[Tuple[int, int], Callable[..., Any]]]: + _, mthname, idlflags, restype, argspec = m + if "propget" in idlflags: + invkind = DISPATCH_PROPERTYGET + mthname = f"_get_{mthname}" + elif "propput" in idlflags: + invkind = DISPATCH_PROPERTYPUT + mthname = f"_set_{mthname}" + elif "propputref" in idlflags: + invkind = DISPATCH_PROPERTYPUTREF + mthname = f"_setref_{mthname}" + else: + invkind = DISPATCH_METHOD + if restype: # DISPATCH_METHOD have implicit "out" + argspec = argspec + ((["out"], restype, ""),) + yield from _make_dispentry(finder, itf, mthname, idlflags, argspec, invkind) + + +def _make_disppropentry( + itf: Type[IUnknown], finder: _MethodFinder, m: "_DispMemberSpec" +) -> Iterator[Tuple[Tuple[int, int], Callable[..., Any]]]: + _, mthname, idlflags, restype, argspec = m + # DISPPROPERTY have implicit "out" + if restype: + argspec += ((["out"], restype, ""),) + yield from _make_dispentry( + finder, itf, f"_get_{mthname}", idlflags, argspec, DISPATCH_PROPERTYGET + ) + if "readonly" not in idlflags: + yield from _make_dispentry( + finder, itf, f"_set_{mthname}", idlflags, argspec, DISPATCH_PROPERTYPUT + ) # Would an early return be better in this case? + # Add DISPATCH_PROPERTYPUTREF also? - def __make_dispentry( - self, - finder: _MethodFinder, - interface: Type[IUnknown], - mthname: str, - idlflags: Tuple[Union[str, int], ...], - argspec: Tuple["_ArgSpecElmType", ...], - invkind: int, - ) -> None: - # We build a _dispmap_ entry now that maps invkind and dispid to - # implementations that the finder finds; IDispatch_Invoke will later call it. - paramflags = [((_encode_idl(x[0]), x[1]) + tuple(x[3:])) for x in argspec] - # XXX can the dispid be at a different index? Check codegenerator. - dispid = idlflags[0] - impl = finder.get_impl(interface, mthname, paramflags, idlflags) - self._dispimpl_[(dispid, invkind)] = impl # type: ignore - # invkind is really a set of flags; we allow both DISPATCH_METHOD and - # DISPATCH_PROPERTYGET (win32com uses this, maybe other languages too?) - if invkind in (DISPATCH_METHOD, DISPATCH_PROPERTYGET): - self._dispimpl_[(dispid, DISPATCH_METHOD | DISPATCH_PROPERTYGET)] = impl + +def _make_dispentry( + finder: _MethodFinder, + interface: Type[IUnknown], + mthname: str, + idlflags: Tuple[_UnionT[str, int], ...], + argspec: Tuple["_ArgSpecElmType", ...], + invkind: int, +) -> Iterator[Tuple[Tuple[int, int], Callable[..., Any]]]: + # We build a _dispmap_ entry now that maps invkind and dispid to + # implementations that the finder finds; IDispatch_Invoke will later call it. + paramflags = tuple(((_encode_idl(x[0]), x[1]) + tuple(x[3:])) for x in argspec) + # XXX can the dispid be at a different index? Check codegenerator. + dispid = idlflags[0] + impl = finder.get_impl(interface, mthname, paramflags, idlflags) # type: ignore + yield ((dispid, invkind), impl) # type: ignore + # invkind is really a set of flags; we allow both DISPATCH_METHOD and + # DISPATCH_PROPERTYGET (win32com uses this, maybe other languages too?) + if invkind in (DISPATCH_METHOD, DISPATCH_PROPERTYGET): + yield ((dispid, DISPATCH_METHOD | DISPATCH_PROPERTYGET), impl) # type: ignore diff --git a/comtypes/hints.pyi b/comtypes/hints.pyi index 2d3793f6..49fa4d9c 100644 --- a/comtypes/hints.pyi +++ b/comtypes/hints.pyi @@ -24,7 +24,7 @@ else: from typing_extensions import Self as Self import comtypes -from comtypes import IUnknown as IUnknown, GUID as GUID +from comtypes import IUnknown as IUnknown, COMObject as COMObject, GUID as GUID from comtypes.automation import IDispatch as IDispatch, VARIANT as VARIANT from comtypes.server import IClassFactory as IClassFactory from comtypes.server import localserver as localserver diff --git a/pyproject.toml b/pyproject.toml index 02b0f92a..41c67a24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ ignore = ["E402"] # production "comtypes/_comobject.py" = ["E713", "E722", "F401"] "comtypes/_npsupport.py" = ["F401"] -"comtypes/_vtbl.py" = ["E713", "E722", "F401"] +"comtypes/_vtbl.py" = ["E722"] "comtypes/connectionpoints.py" = ["F401", "F403", "F405"] "comtypes/automation.py" = ["F401", "F403", "F405"] "comtypes/errorinfo.py" = ["F403", "F405"]