diff --git a/comtypes/__init__.py b/comtypes/__init__.py index 86e54b30..6eb4ab1f 100644 --- a/comtypes/__init__.py +++ b/comtypes/__init__.py @@ -176,6 +176,7 @@ def CoUninitialize(): # IUnknown, the root of all evil... from comtypes._post_coinit import _shutdown from comtypes._post_coinit.unknwn import IUnknown # noqa +from comtypes._post_coinit.base import _compointer_base # noqa atexit.register(_shutdown) diff --git a/comtypes/_post_coinit/base.py b/comtypes/_post_coinit/base.py new file mode 100644 index 00000000..7645877c --- /dev/null +++ b/comtypes/_post_coinit/base.py @@ -0,0 +1,105 @@ +from ctypes import c_void_p +import logging + +from comtypes._post_coinit.unknwn import _cominterface_meta + + +logger = logging.getLogger(__name__) + + +class _compointer_meta(type(c_void_p), _cominterface_meta): + "metaclass for COM interface pointer classes" + # no functionality, but needed to avoid a metaclass conflict + + +class _compointer_base(c_void_p, metaclass=_compointer_meta): + "base class for COM interface pointer classes" + + def __del__(self, _debug=logger.debug): + "Release the COM refcount we own." + if self: + # comtypes calls CoUninitialize() when the atexit handlers + # runs. CoUninitialize() cleans up the COM objects that + # are still alive. Python COM pointers may still be + # present but we can no longer call Release() on them - + # this may give a protection fault. So we need the + # _com_shutting_down flag. + # + if not type(self)._com_shutting_down: + _debug("Release %s", self) + self.Release() + + def __cmp__(self, other): + """Compare pointers to COM interfaces.""" + # COM identity rule + # + # XXX To compare COM interface pointers, should we + # automatically QueryInterface for IUnknown on both items, and + # compare the pointer values? + if not isinstance(other, _compointer_base): + return 1 + + # get the value property of the c_void_p baseclass, this is the pointer value + return cmp( + super(_compointer_base, self).value, super(_compointer_base, other).value + ) + + def __eq__(self, other): + if not isinstance(other, _compointer_base): + return False + # get the value property of the c_void_p baseclass, this is the pointer value + return ( + super(_compointer_base, self).value == super(_compointer_base, other).value + ) + + def __hash__(self): + """Return the hash value of the pointer.""" + # hash the pointer values + return hash(super(_compointer_base, self).value) + + # redefine the .value property; return the object itself. + def __get_value(self): + return self + + value = property(__get_value, doc="""Return self.""") + + def __repr__(self): + ptr = super(_compointer_base, self).value + return "<%s ptr=0x%x at %x>" % (self.__class__.__name__, ptr or 0, id(self)) + + # This fixes the problem when there are multiple python interface types + # wrapping the same COM interface. This could happen because some interfaces + # are contained in multiple typelibs. + # + # It also allows to pass a CoClass instance to an api + # expecting a COM interface. + @classmethod + def from_param(cls, value): + """Convert 'value' into a COM pointer to the interface. + + This method accepts a COM pointer, or a CoClass instance + which is QueryInterface()d.""" + if value is None: + return None + # CLF: 2013-01-18 + # A default value of 0, meaning null, can pass through to here. + if value == 0: + return None + if isinstance(value, cls): + return value + # multiple python interface types for the same COM interface. + # Do we need more checks here? + if cls._iid_ == getattr(value, "_iid_", None): + return value + # Accept an CoClass instance which exposes the interface required. + try: + table = value._com_pointers_ + except AttributeError: + pass + else: + try: + # a kind of QueryInterface + return table[cls._iid_] + except KeyError: + raise TypeError("Interface %s not supported" % cls._iid_) + return value.QueryInterface(cls.__com_interface__) diff --git a/comtypes/_post_coinit/unknwn.py b/comtypes/_post_coinit/unknwn.py index 046d1bc2..85874afc 100644 --- a/comtypes/_post_coinit/unknwn.py +++ b/comtypes/_post_coinit/unknwn.py @@ -84,6 +84,7 @@ def __new__(cls, name, bases, namespace): # then we need to make sure that POINTER(IDispatch) is a # subclass of POINTER(IUnknown) because of the way ctypes # typechecks work. + from comtypes._post_coinit.base import _compointer_base if bases == (object,): _ptr_bases = (new_cls, _compointer_base) else: @@ -360,107 +361,6 @@ def _make_methods(self, methods: List[_ComMemberSpec]) -> None: self.__map_case__[name.lower()] = name -################################################################ - - -class _compointer_meta(type(c_void_p), _cominterface_meta): - "metaclass for COM interface pointer classes" - # no functionality, but needed to avoid a metaclass conflict - - -class _compointer_base(c_void_p, metaclass=_compointer_meta): - "base class for COM interface pointer classes" - - def __del__(self, _debug=logger.debug): - "Release the COM refcount we own." - if self: - # comtypes calls CoUninitialize() when the atexit handlers - # runs. CoUninitialize() cleans up the COM objects that - # are still alive. Python COM pointers may still be - # present but we can no longer call Release() on them - - # this may give a protection fault. So we need the - # _com_shutting_down flag. - # - if not type(self)._com_shutting_down: - _debug("Release %s", self) - self.Release() - - def __cmp__(self, other): - """Compare pointers to COM interfaces.""" - # COM identity rule - # - # XXX To compare COM interface pointers, should we - # automatically QueryInterface for IUnknown on both items, and - # compare the pointer values? - if not isinstance(other, _compointer_base): - return 1 - - # get the value property of the c_void_p baseclass, this is the pointer value - return cmp( - super(_compointer_base, self).value, super(_compointer_base, other).value - ) - - def __eq__(self, other): - if not isinstance(other, _compointer_base): - return False - # get the value property of the c_void_p baseclass, this is the pointer value - return ( - super(_compointer_base, self).value == super(_compointer_base, other).value - ) - - def __hash__(self): - """Return the hash value of the pointer.""" - # hash the pointer values - return hash(super(_compointer_base, self).value) - - # redefine the .value property; return the object itself. - def __get_value(self): - return self - - value = property(__get_value, doc="""Return self.""") - - def __repr__(self): - ptr = super(_compointer_base, self).value - return "<%s ptr=0x%x at %x>" % (self.__class__.__name__, ptr or 0, id(self)) - - # This fixes the problem when there are multiple python interface types - # wrapping the same COM interface. This could happen because some interfaces - # are contained in multiple typelibs. - # - # It also allows to pass a CoClass instance to an api - # expecting a COM interface. - @classmethod - def from_param(cls, value): - """Convert 'value' into a COM pointer to the interface. - - This method accepts a COM pointer, or a CoClass instance - which is QueryInterface()d.""" - if value is None: - return None - # CLF: 2013-01-18 - # A default value of 0, meaning null, can pass through to here. - if value == 0: - return None - if isinstance(value, cls): - return value - # multiple python interface types for the same COM interface. - # Do we need more checks here? - if cls._iid_ == getattr(value, "_iid_", None): - return value - # Accept an CoClass instance which exposes the interface required. - try: - table = value._com_pointers_ - except AttributeError: - pass - else: - try: - # a kind of QueryInterface - return table[cls._iid_] - except KeyError: - raise TypeError("Interface %s not supported" % cls._iid_) - return value.QueryInterface(cls.__com_interface__) - - ################################################################ # IUnknown, the root of all evil...