You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
5.9 KiB
144 lines
5.9 KiB
from ctypes import *
|
|
from comtypes import IUnknown, COMObject, COMError
|
|
from comtypes.hresult import *
|
|
from comtypes.typeinfo import LoadRegTypeLib
|
|
from comtypes.connectionpoints import IConnectionPoint
|
|
from comtypes.automation import IDispatch
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
__all__ = ["ConnectableObjectMixin"]
|
|
|
|
class ConnectionPointImpl(COMObject):
|
|
"""This object implements a connectionpoint"""
|
|
_com_interfaces_ = [IConnectionPoint]
|
|
|
|
def __init__(self, sink_interface, sink_typeinfo):
|
|
super(ConnectionPointImpl, self).__init__()
|
|
self._connections = {}
|
|
self._cookie = 0
|
|
self._sink_interface = sink_interface
|
|
self._typeinfo = sink_typeinfo
|
|
|
|
# per MSDN, all interface methods *must* be implemented, E_NOTIMPL
|
|
# is no allowed return value
|
|
|
|
def IConnectionPoint_Advise(self, this, pUnk, pdwCookie):
|
|
if not pUnk or not pdwCookie:
|
|
return E_POINTER
|
|
logger.debug("Advise")
|
|
try:
|
|
ptr = pUnk.QueryInterface(self._sink_interface)
|
|
except COMError:
|
|
return CONNECT_E_CANNOTCONNECT
|
|
pdwCookie[0] = self._cookie = self._cookie + 1
|
|
self._connections[self._cookie] = ptr
|
|
return S_OK
|
|
|
|
def IConnectionPoint_Unadvise(self, this, dwCookie):
|
|
logger.debug("Unadvise %s", dwCookie)
|
|
try:
|
|
del self._connections[dwCookie]
|
|
except KeyError:
|
|
return CONNECT_E_NOCONNECTION
|
|
return S_OK
|
|
|
|
def IConnectionPoint_GetConnectionPointContainer(self, this, ppCPC):
|
|
return E_NOTIMPL
|
|
|
|
def IConnectionPoint_GetConnectionInterface(self, this, pIID):
|
|
return E_NOTIMPL
|
|
|
|
def _call_sinks(self, name, *args, **kw):
|
|
results = []
|
|
logger.debug("_call_sinks(%s, %s, *%s, **%s)", self, name, args, kw)
|
|
# Is it an IDispatch derived interface? Then, events have to be delivered
|
|
# via Invoke calls (even if it is a dual interface).
|
|
if hasattr(self._sink_interface, "Invoke"):
|
|
# for better performance, we could cache the dispids.
|
|
dispid = self._typeinfo.GetIDsOfNames(name)[0]
|
|
for key, p in list(self._connections.items()):
|
|
try:
|
|
result = p.Invoke(dispid, *args, **kw)
|
|
except COMError as details:
|
|
if details.hresult == -2147023174:
|
|
logger.warning("_call_sinks(%s, %s, *%s, **%s) failed; removing connection",
|
|
self, name, args, kw,
|
|
exc_info=True)
|
|
try:
|
|
del self._connections[key]
|
|
except KeyError:
|
|
pass # connection already gone
|
|
else:
|
|
logger.warning("_call_sinks(%s, %s, *%s, **%s)", self, name, args, kw,
|
|
exc_info=True)
|
|
else:
|
|
results.append(result)
|
|
else:
|
|
for p in list(self._connections.values()):
|
|
try:
|
|
result = getattr(p, name)(*args, **kw)
|
|
except COMError as details:
|
|
if details.hresult == -2147023174:
|
|
logger.warning("_call_sinks(%s, %s, *%s, **%s) failed; removing connection",
|
|
self, name, args, kw,
|
|
exc_info=True)
|
|
del self._connections[key]
|
|
else:
|
|
logger.warning("_call_sinks(%s, %s, *%s, **%s)", self, name, args, kw,
|
|
exc_info=True)
|
|
else:
|
|
results.append(result)
|
|
return results
|
|
|
|
class ConnectableObjectMixin(object):
|
|
"""Mixin which implements IConnectionPointContainer.
|
|
|
|
Call Fire_Event(interface, methodname, *args, **kw) to fire an
|
|
event. <interface> can either be the source interface, or an
|
|
integer index into the _outgoing_interfaces_ list.
|
|
"""
|
|
def __init__(self):
|
|
super(ConnectableObjectMixin, self).__init__()
|
|
self.__connections = {}
|
|
|
|
tlib = LoadRegTypeLib(*self._reg_typelib_)
|
|
for itf in self._outgoing_interfaces_:
|
|
typeinfo = tlib.GetTypeInfoOfGuid(itf._iid_)
|
|
self.__connections[itf] = ConnectionPointImpl(itf, typeinfo)
|
|
|
|
def IConnectionPointContainer_EnumConnectionPoints(self, this, ppEnum):
|
|
# according to MSDN, E_NOTIMPL is specificially disallowed
|
|
# because, without typeinfo, there's no way for the caller to
|
|
# find out.
|
|
return E_NOTIMPL
|
|
|
|
def IConnectionPointContainer_FindConnectionPoint(self, this, refiid, ppcp):
|
|
iid = refiid[0]
|
|
logger.debug("FindConnectionPoint %s", iid)
|
|
if not ppcp:
|
|
return E_POINTER
|
|
for itf in self._outgoing_interfaces_:
|
|
if itf._iid_ == iid:
|
|
# 'byref' will not work in this case, since the QueryInterface
|
|
# method implementation is called on Python directly. There's
|
|
# no C layer between which will convert the second parameter
|
|
# from byref() to pointer().
|
|
conn = self.__connections[itf]
|
|
result = conn.IUnknown_QueryInterface(None, pointer(IConnectionPoint._iid_), ppcp)
|
|
logger.debug("connectionpoint found, QI() -> %s", result)
|
|
return result
|
|
logger.debug("No connectionpoint found")
|
|
return CONNECT_E_NOCONNECTION
|
|
|
|
def Fire_Event(self, itf, name, *args, **kw):
|
|
# Fire event 'name' with arguments *args and **kw.
|
|
# Accepts either an interface index or an interface as first argument.
|
|
# Returns a list of results.
|
|
logger.debug("Fire_Event(%s, %s, *%s, **%s)", itf, name, args, kw)
|
|
if isinstance(itf, int):
|
|
itf = self._outgoing_interfaces_[itf]
|
|
return self.__connections[itf]._call_sinks(name, *args, **kw)
|
|
|