Re: Connection Points Implementation

"Shane Holloway (IEEE)" <[email protected]> Mon, 23 May 2005 12:01:12 -0600
Newsgroups gmane.comp.python.ctypes.devel
Message-ID <[email protected]>
Thomas Heller wrote:
> Shane, thanks for the patch.  I have my own version of this patch, but 
> yours is probably better.  My own one only supports a single outgoing 
> interface, but yours looks like it supports more of them.  As a bonus, 
> my version actually supports to 'fire' the events, and even from 
> different threads.  I'll try to merge both and check them into CVS.  The 
> approach to fire the events is quite simple, here it is (I hope 
> thunderbird doesn't destroy the formatting):

<snip>

Ok, I tried to implement the fireEvent in what I have, but I keep 
getting an exception that says::

   TypeError: function takes at most 3 arguments (9 given)

For sink.Invoke().  It obviously takes 9 parameters, but I can't seem to 
convince the system of that.  ;)  Any thoughts? ::

         def fireEvent(*args):
             for sink in self._iterConnectionPointSinks():
                 params = DISPPARAMS()
                 params.cArgs = len(args)
                 rgvarg = params.rgvarg = (VARIANT * len(args))()
                 for i, a in enumerate(args):
                     rgvarg[i].value = a

                 Invoke = dict(sink._methods_)['Invoke']
                 Invoke(
                     sink, dispid,
                     byref(GUID()), 0, # lcid
                     DISPATCH_METHOD, # wFlags
                     byref(params),
                     None, # pVarResult
                     None, # pExcepInfo
                     None) # puArgError


The patch of my attempt is attached.

Thanks,
-Shane
ctypes-ServerConnectionPoints-2005.05.23.patch (text/plain, 12.9 KB)
Index: win32/com/connectionpoints.py
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/connectionpoints.py,v
retrieving revision 1.10
diff -u -r1.10 connectionpoints.py
--- win32/com/connectionpoints.py	21 Apr 2004 18:07:34 -0000	1.10
+++ win32/com/connectionpoints.py	23 May 2005 17:51:54 -0000
@@ -1,6 +1,7 @@
 # connect.py - ConnectionPoint support
 from ctypes import *
-from ctypes.com import IUnknown, GUID, REFIID, STDMETHOD, HRESULT, COMObject
+from ctypes.com import IUnknown, GUID, REFIID, STDMETHOD, HRESULT, COMObject, E_NOTIMPL, hresult
+from ctypes.com.automation import VARIANT, DISPPARAMS, DISPATCH_METHOD
 from ctypes.wintypes import DWORD
 
 ################
@@ -114,3 +115,153 @@
     def disconnect(self, (cp, cookie)):
         # disconnect. Call this with the data returned by connect()
         cp.Unadvise(cookie)
+
+################################################################
+# A Base class for implementing ConnectionPoints
+#
+
+class ConnectableEventBinding(object):
+    _connectionPointSinks = None
+    _eventInterface = None
+
+    def __init__(self, eventInterface):
+        self._eventInterface = eventInterface
+        self._bindDispMethods(eventInterface)
+
+    def _getConnectionPointSinks(self):
+        if self._connectionPointSinks is None:
+            self._connectionPointSinks = {}
+        return self._connectionPointSinks
+
+    def _addConnectionSink(self, cookie, sink):
+        self._getConnectionPointSinks()[cookie] = sink
+    def _removeConnectionSink(self, cookie):
+        return self._getConnectionPointSinks().pop(cookie, None) is not None
+
+    def _iterConnectionPointSinks(self):
+        return self._getConnectionPointSinks().itervalues()
+
+    def _bindDispMethods(self, eventInterface):
+        for dispid, name, stdmethod in eventInterface._dispmethods_:
+            method = self._bindMethod(dispid, name)
+            if method is not None:
+                setattr(self, name, method)
+
+    def _bindMethod(self, dispid, methodName):
+        def fireEvent(*args):
+            for sink in self._iterConnectionPointSinks():
+                params = DISPPARAMS()
+                params.cArgs = len(args)
+                rgvarg = params.rgvarg = (VARIANT * len(args))()
+                for i, a in enumerate(args):
+                    rgvarg[i].value = a
+
+                Invoke = dict(sink._methods_)['Invoke']
+                print
+                print '*************'
+                print 'method:', methodName
+                print 'sink:', sink
+                print 'dispid:', dispid
+                print 'args:', args
+                print
+                print 'invoke:', Invoke, type(Invoke)
+                print ' flags:', Invoke._flags_
+                print '   res:', Invoke.restype, Invoke._restype_
+                print '  args:', Invoke.argtypes, Invoke._argtypes_
+                print '*************'
+                print
+                Invoke(
+                    sink, dispid,
+                    byref(GUID()), 0, # lcid
+                    DISPATCH_METHOD, # wFlags
+                    byref(params),
+                    None, # pVarResult
+                    None, # pExcepInfo
+                    None) # puArgError
+
+        fireEvent.__name__ = methodName
+        return fireEvent
+
+
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class ConnectableMixin(object):
+    EventBindingFactory = ConnectableEventBinding
+    _com_interfaces_ = [IConnectionPoint, IConnectionPointContainer]
+    _outgoing_interfaces_ = []
+    _connectableBindings = None
+    _connectionPointCookie = 0
+
+    def defaultEvent(self):
+        return self.getEventBindingFor(self._outgoing_interfaces_[0])
+
+    def getEventBindings(self):
+        if self._connectableBindings is None:
+            self._connectableBindings = {}
+        return self._connectableBindings
+
+    def iterEventBindings(self):
+        return self.getEventBindings().itervalues()
+
+    def getEventBindingFor(self, eventInterface):
+        bindings = self.getEventBindings()
+        try:
+            return bindings[eventInterface]
+        except LookupError:
+            eventBinding = self.EventBindingFactory(eventInterface)
+            bindings[eventInterface] = eventBinding
+            return eventBinding
+
+    def addConnectionPointSink(self, interface, connectionSink, cookie=None):
+        if cookie is None:
+            self._connectionPointCookie += 1
+            cookie = self._connectionPointCookie
+        self.getEventBindingFor(interface)._addConnectionSink(cookie, connectionSink)
+        return cookie
+
+    def removeConnectionPointSink(self, cookie):
+        result = False
+        for binding in self.iterEventBindings():
+            if binding._removeConnectionSink(cookie):
+                result = True
+        return result
+
+    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def IConnectionPoint_GetConnectionInterface(self, this, guid):
+        return hresult.E_NOTIMPL
+    def IConnectionPoint_GetConnectionPointContainer(self, this, retIConnectionPointContainer):
+        return hresult.E_NOTIMPL
+    def IConnectionPoint_EnumConnections(self, piEnumConnections):
+        return hresult.E_NOTIMPL
+
+    def IConnectionPoint_Advise(self, this, piUnknown, pCookie):
+        cookie = None
+        for interface in self._outgoing_interfaces_:
+            connectionSink = POINTER(interface)()
+            piUnknown.QueryInterface(byref(interface._iid_), byref(connectionSink))
+
+            if connectionSink[0]:
+                cookie = self.addConnectionPointSink(interface, connectionSink[0], cookie)
+
+        if cookie is not None:
+            pCookie[0] = cookie
+            return hresult.S_OK
+        else:
+            return hresult.E_NOINTERFACE
+
+    def IConnectionPoint_Unadvise(self, this, cookie):
+        if self.removeConnectionPointSink(cookie):
+            return hresult.S_OK
+        else:
+            return hresult.E_UNEXPECTED
+
+    def IConnectionPointContainer_EnumConnectionPoints(self, this, piEnumConnectionPoints):
+        raise NotImplementedError() #return hresult.E_NOTIMPL
+    def IConnectionPointContainer_FindConnectionPoint(self, this, iidConnectionPoint, retIConnectionPoint):
+        cp = iidConnectionPoint[0]
+        if iidConnectionPoint[0] not in [x._iid_ for x in self._outgoing_interfaces_]:
+            return hresult.E_NOINTERFACE
+
+        return self.QueryInterface(this, [IConnectionPoint._iid_], retIConnectionPoint)
+
Index: win32/com/server.py
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/server.py,v
retrieving revision 1.20
diff -u -r1.20 server.py
--- win32/com/server.py	20 Aug 2004 14:10:03 -0000	1.20
+++ win32/com/server.py	17 May 2005 19:25:28 -0000
@@ -49,6 +49,7 @@
 
     def install(cls):
         import sys
+        return # TODO: Remove SWH
         sys.stdout = sys.stderr = cls()
     install = classmethod(install)
 
Index: win32/com/samples/server/sum.idl
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/samples/server/sum.idl,v
retrieving revision 1.3
diff -u -r1.3 sum.idl
--- win32/com/samples/server/sum.idl	15 Jan 2004 18:49:32 -0000	1.3
+++ win32/com/samples/server/sum.idl	23 May 2005 17:49:20 -0000
@@ -9,6 +9,18 @@
 {
 	importlib("stdole2.tlb");
 	coclass CSum;
+
+    [
+      uuid(8312BA64-DC22-4C1A-BC07-007B89C092A8),
+      helpstring("_IDualSumEvents Interface")
+    ]
+    dispinterface _IDualSumEvents {
+        properties:
+        methods:
+            [id(0x00002005), helpstring("method OnAdd")]
+            HRESULT OnAdd([in] double a, double b, double result);
+    };
+
 	/* a dual interface, derived from IDispatch */
 	[
 		object,
@@ -29,5 +41,6 @@
 	coclass CSum
 	{
 		[default] interface IDualSum;
+        [default, source] dispinterface _IDualSumEvents;
 	}
 };
Index: win32/com/samples/server/sum.py
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/samples/server/sum.py,v
retrieving revision 1.8
diff -u -r1.8 sum.py
--- win32/com/samples/server/sum.py	9 Jul 2003 19:08:19 -0000	1.8
+++ win32/com/samples/server/sum.py	20 May 2005 20:28:20 -0000
@@ -11,7 +11,7 @@
 # Then, we can import what we need from the sum_gen module:
 
 # interface, coclass, typelib
-from sum_gen import IDualSum, CSum, SumLib
+from sum_gen import IDualSum, CSum, SumLib, _IDualSumEvents
 
 # special code for this sample, to make sure sum_gen contains
 # a valid path to the type library:
@@ -26,10 +26,12 @@
 # and ctypes.com provides a handy bas class we can use:
 from ctypes.com.automation import DualObjImpl
 from ctypes.com.server import CLSCTX_LOCAL_SERVER, CLSCTX_INPROC_SERVER
+from ctypes.com.connectionpoints import ConnectableMixin
 
-class SumObject(DualObjImpl):
+class SumObject(DualObjImpl, ConnectableMixin):
     # A sequence of COM interfaces this object implements
-    _com_interfaces_ = [IDualSum]
+    _com_interfaces_ = [IDualSum] + ConnectableMixin._com_interfaces_
+    _outgoing_interfaces_ = [_IDualSumEvents]
     # The type library we need, SumLib has the correct attributes
     _typelib_ = SumLib
     # The progid for the registry
@@ -62,6 +64,10 @@
         # floats, and presult is the result pointer which also points
         # to a c_double.
         presult[0] = a + b
+
+        # fire the callbacks
+        self.defaultEvent().OnAdd(a, b, presult[0])
+
         # The method must return a HRESULT, 0 is the same as S_OK
         return 0
 
Index: win32/com/samples/server/sum.tlb
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/samples/server/sum.tlb,v
retrieving revision 1.3
diff -u -r1.3 sum.tlb
Binary files /tmp/cvslU5URW and sum.tlb differ
Index: win32/com/samples/server/sum_gen.py
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/samples/server/sum_gen.py,v
retrieving revision 1.4
diff -u -r1.4 sum_gen.py
--- win32/com/samples/server/sum_gen.py	3 Jul 2003 20:06:37 -0000	1.4
+++ win32/com/samples/server/sum_gen.py	23 May 2005 17:49:56 -0000
@@ -1,5 +1,5 @@
 # -*- python -*-
-# Generated from C:\sf\ctypes_head\win32\com\samples\server\sum.tlb
+# Generated from c:\3rdParty\pyKeepers.src\tools and libs\ctypes\win32\com\samples\server\sum.tlb
 
 ###############################################################
 # NOTE: This is a GENERATED file. Please do not make changes, #
@@ -20,7 +20,7 @@
     guid = GUID('{90810CB9-D427-48B6-81FF-92D4A2098B45}')
     version = (1, 0)
     flags = 0x8
-    path = 'C:\\sf\\ctypes_head\\win32\\com\\samples\\server\\sum.tlb'
+    path = 'c:\\3rdParty\\pyKeepers.src\\tools and libs\\ctypes\\win32\\com\\samples\\server\\sum.tlb'
 
 ##############################################################################
 
@@ -29,14 +29,24 @@
     _iid_ = GUID('{6EDC65BF-0CB7-4B0D-9E43-11C655E51AE9}')
 
 
+class _IDualSumEvents(dispinterface):
+    """_IDualSumEvents Interface"""
+    _iid_ = GUID('{8312BA64-DC22-4C1A-BC07-007B89C092A8}')
+
+
 IDualSum._methods_ = IDispatch._methods_ + [
     STDMETHOD(HRESULT, "Add", c_double, c_double, POINTER(c_double)),
 ]
 
+_IDualSumEvents._dispmethods_ = [
+    DISPMETHOD(0x2005L, HRESULT, "OnAdd", c_double, c_double, c_double),
+]
+
 ##############################################################################
 
 class CSum:
     """CSum Class"""
     _reg_clsid_ = '{2E0504A1-1A23-443F-939D-869A6C731521}'
     _com_interfaces_ = [IDualSum]
+    _outgoing_interfaces_ = [_IDualSumEvents]
 
Index: win32/com/samples/server/sum_user.py
===================================================================
RCS file: /cvsroot/ctypes/ctypes/win32/com/samples/server/sum_user.py,v
retrieving revision 1.3
diff -u -r1.3 sum_user.py
--- win32/com/samples/server/sum_user.py	28 May 2003 19:53:43 -0000	1.3
+++ win32/com/samples/server/sum_user.py	17 May 2005 19:23:00 -0000
@@ -4,7 +4,8 @@
 from ctypes import *
 from ctypes.com import CreateInstance
 from ctypes.com.automation import IDispatch
-from sum_gen import CSum
+from ctypes.com.connectionpoints import dispinterface_EventReceiver
+from sum_gen import CSum, _IDualSumEvents
 
 CLSCTX_INPROC_SERVER = 0x1
 CLSCTX_LOCAL_SERVER = 0x4
@@ -17,11 +18,23 @@
     print "Using CLSCTX_LOCAL_SERVER"
     clsctx = CLSCTX_LOCAL_SERVER
 
+class SumEvents(dispinterface_EventReceiver):
+    _com_interfaces_ = [_IDualSumEvents]
+    
+    def _IDualSumEvents_AnAddEvent(self, this, a, b, result):
+        print "OnAdd", a, b, result
+
 sum = CreateInstance(CSum, clsctx=clsctx)
 result = c_double()
 sum.Add(3.14, 3.14, byref(result))
 print "Added 3.14 and 3.14, result", result.value
 
+events = SumEvents()
+cookie = events.connect(sum)
+sum.Add(2.718, 2.718, byref(result))
+print "Added 2.718 and 2.718, result", result.value
+events.disconnect(cookie)
+
 idisp = pointer(IDispatch())
 
 sum.QueryInterface(byref(IDispatch._iid_), byref(idisp))