IConnectionPoint interface
Connects a client’s sink to one outgoing interface of an object, and disconnects it again. It is the COM mechanism that delivers events: every class that declares Event members is a connectable object, and a WithEvents variable connects to it through this interface. IConnectionPointContainer, the interface that finds the connection points of an object, has a section of its own below.
- How connection points work
- Declaration
- IConnectionPoint methods
- IConnectionPointContainer
- The enumerators
- Events in twinBASIC
- Example
- See Also
How connection points work
An object that raises events, the source, describes them as an outgoing interface: an interface that the source calls, and that the client implements. The client’s implementation of it is the sink. The connection is made in five steps:
- The source supports IConnectionPointContainer, so the client asks it for that interface with QueryInterface.
- The client asks the container for the connection point of the outgoing interface, by the interface’s identifier with FindConnectionPoint, or by listing every point with EnumConnectionPoints.
- The client creates the sink and passes it to the connection point’s Advise method. The connection point asks the sink for the outgoing interface, keeps the pointer it gets, and returns a cookie: a number that identifies this connection.
- When an event occurs, the source calls the matching method of every connected sink, in the order the connections were made.
- The client passes the cookie to Unadvise. The source releases its pointer to the sink.
The outgoing interface of an object is usually a dispinterface, an interface whose methods are reached only through IDispatch. A sink then receives each event as a call to its Invoke method, with the dispatch identifier of the event and the event’s arguments in a DISPPARAMS structure.
Declaration
IConnectionPoint derives from IUnknown and has the interface identifier B196B286-BAB4-101A-B69C-00AA00341D07. Its five methods return an HRESULT, which twinBASIC hides as it does for any interface method: a failure code raises a run-time error, and a success code other than S_OK is read with Err.LastHresult. A method whose last parameter is an output pointer is declared as a Function that returns it.
stdole has none of the interfaces on this page: a variable declared As stdole.IConnectionPoint is a compile error (TB5079, Unrecognised datatype symbol). A project declares its own copies, with the identifiers below.
The structures first, GUID for an interface identifier and CONNECTDATA for one item of an enumeration of connections:
Module ConnectionTypes
Public Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
Public Type CONNECTDATA
pUnk As stdole.IUnknown
dwCookie As Long
End Type
End Module
The two interfaces this page describes, and the two enumerators they return:
[InterfaceId("B196B284-BAB4-101A-B69C-00AA00341D07")]
Private Interface IConnectionPointContainer Extends stdole.IUnknown
Function EnumConnectionPoints() As IEnumConnectionPoints
Function FindConnectionPoint(ByRef riid As GUID) As IConnectionPoint
End Interface
[InterfaceId("B196B286-BAB4-101A-B69C-00AA00341D07")]
Private Interface IConnectionPoint Extends stdole.IUnknown
Sub GetConnectionInterface(ByRef piid As GUID)
Function GetConnectionPointContainer() As IConnectionPointContainer
Function Advise(ByVal pUnkSink As stdole.IUnknown) As Long
Sub Unadvise(ByVal dwCookie As Long)
Function EnumConnections() As IEnumConnections
End Interface
[InterfaceId("B196B285-BAB4-101A-B69C-00AA00341D07")]
Private Interface IEnumConnectionPoints Extends stdole.IUnknown
Sub Next(ByVal cConnections As Long, ByRef ppCP As IConnectionPoint, ByRef pcFetched As Long)
Sub Skip(ByVal cConnections As Long)
Sub Reset()
Function Clone() As IEnumConnectionPoints
End Interface
[InterfaceId("B196B287-BAB4-101A-B69C-00AA00341D07")]
Private Interface IEnumConnections Extends stdole.IUnknown
Sub Next(ByVal cConnections As Long, ByRef rgcd As CONNECTDATA, ByRef pcFetched As Long)
Sub Skip(ByVal cConnections As Long)
Sub Reset()
Function Clone() As IEnumConnections
End Interface
ppCP and rgcd name the first element of the caller’s array and nothing after it, which is enough to ask for one item at a time. The enumerators of this page return one item per call whatever the count asked for (see The enumerators).
The identifier of the outgoing interface of a class written in twinBASIC is not a fixed value, so a program never declares it. It reads the identifier from the connection point (see Events in twinBASIC).
IConnectionPoint methods
GetConnectionInterface
Returns the identifier of the outgoing interface that the connection point serves.
Syntax: object.GetConnectionInterface piid
- piid
- required A GUID that receives the interface identifier.
The identifier is the one to pass to FindConnectionPoint to get the same connection point again, and the one a sink must answer to when it is advised.
GetConnectionPointContainer
Returns the container that the connection point belongs to.
Syntax: Set container = object.GetConnectionPointContainer()
The result is the object the connection point was found on, so a client that holds only the connection point can reach the other points of its source.
Advise
Connects a sink to the connection point.
Syntax: cookie = object.Advise( pUnkSink )
- pUnkSink
- required The sink. The connection point asks it for the outgoing interface, so it must answer QueryInterface for that interface’s identifier.
Returns a Long, the cookie that identifies the connection. The cookie is not zero when a connection was made, and no two connections of one connection point have the same one. The connection point holds a reference to the sink until the connection ends.
In twinBASIC:
- Cookies start at 1 for each source object and increase by one for every connection made. A cookie is not used again after its connection ends.
- A sink that does not answer for the outgoing interface fails with
E_NOINTERFACE(&H80004002), where the COM contract namesCONNECT_E_CANNOTCONNECT. An object of an ordinary twinBASIC class is such a sink, even when the class implements IDispatch, and so is a class declared NotDispatchable that implements it. A twinBASIC class cannot answer for the identifier, because the identifier of a class’s event interface changes with every build and so cannot be declared. A sink that works is the object twinBASIC creates for a WithEvents variable, which EnumConnections returns (see the example). - A sink that is already connected to the point is not connected a second time. Advise raises no error, returns 0, and adds nothing.
- Passing Nothing ends the run with an access violation in BETA 995. The COM contract returns
E_POINTER.
Unadvise
Ends a connection.
Syntax: object.Unadvise dwCookie
- dwCookie
- required A Long: the cookie that Advise returned.
The connection point releases the reference it held to the sink, and the source stops calling it. An event that the source raises afterwards does not reach that sink.
In twinBASIC a cookie that names no connection, 0 included, raises no error, where the COM contract reports one. A WithEvents variable whose connection was ended with Unadvise can still be set to Nothing without an error.
EnumConnections
Returns an enumerator over the connections that exist now.
Syntax: Set connections = object.EnumConnections()
Each item is a CONNECTDATA: pUnk is the sink, with a reference added that the caller owns, and dwCookie is the cookie of its connection. The items come in the order the connections were made. A caller counts the connections of a point, or finds the cookie of a sink, with it.
IConnectionPointContainer
IConnectionPointContainer derives from IUnknown and has the interface identifier B196B284-BAB4-101A-B69C-00AA00341D07. An object that raises events supports it, with one connection point for each outgoing interface.
EnumConnectionPoints
Returns an enumerator over the connection points of the object.
Syntax: Set points = object.EnumConnectionPoints()
The enumerator is an IEnumConnectionPoints, which returns IConnectionPoint items.
FindConnectionPoint
Returns the connection point for one outgoing interface.
Syntax: Set point = object.FindConnectionPoint( riid )
- riid
- required A GUID: the identifier of the outgoing interface.
Raises CONNECT_E_NOCONNECTION (&H80040200) when the object has no outgoing interface with that identifier. Called again with the same identifier it returns the same connection point object that EnumConnectionPoints returns.
The enumerators
IEnumConnectionPoints and IEnumConnections follow the pattern of IEnumVARIANT: Next, Skip, Reset and Clone. In BETA 995 the two enumerators that twinBASIC supplies do not follow it in the same way, so a caller reads each one as described here. Both are read one item at a time, with cConnections of 1.
| Method | IEnumConnectionPoints | IEnumConnections |
|---|---|---|
| Next | Returns one item and S_OK. When no item is left, raises E_FAIL (&H80004005) and sets pcFetched to 0, where the contract returns S_FALSE. Asking for more items than are left also raises E_FAIL. | Returns one item and S_OK, even when more were asked for and more are left. When no item is left, writes nothing, sets pcFetched to 0 and returns S_FALSE, so a loop ends when pcFetched is 0. A null pcFetched is accepted. |
| Skip | Raises E_NOTIMPL (&H80004001). | Moves past the items. |
| Reset | Moves back to the start. | Moves back to the start. |
| Clone | Raises E_NOTIMPL. | Returns a new enumerator, which reads on its own. |
A caller that asks for several items at once from IEnumConnections therefore gets only the first, and must call again. A caller that reads IEnumConnectionPoints handles the error of the call that finds the end.
Events in twinBASIC
A class that declares Event members is a connectable object, and the compiler writes all of its connection point support. What it does, in BETA 995:
- Every class answers for IConnectionPointContainer. QueryInterface, and a Set to a variable of the container type, succeed for a class with no events as they do for a class with events.
- A class with events has one connection point, however many events it declares. EnumConnectionPoints returns it and FindConnectionPoint finds it by its interface identifier.
- A class with no events has none, and says so by failing. EnumConnectionPoints and FindConnectionPoint both raise run-time error 445, Object doesn’t support this action.
- The identifier of the outgoing interface is generated for each class. Every object of one class reports the same identifier, two classes report different ones, and rebuilding the project changes them. Read it with GetConnectionInterface.
- The outgoing interface is a dispinterface. The sink twinBASIC creates answers for the interface’s identifier with its IDispatch pointer. Each event has a dispatch identifier: 1 for the first event the class declares, 2 for the second, and so on in declaration order. A call of a dispatch identifier on the sink runs the handler of that event, and a late-bound CallByDispId on a sink does the same.
- A WithEvents variable is a connection. Assigning an object to it with Set calls Advise, which adds one connection to the source. Assigning Nothing, assigning another object, and destroying the object that holds the variable each call Unadvise on the source the variable held, which removes that connection. Two objects that watch the same source make two connections, and the handlers run in the order the connections were made. The sink object that twinBASIC registers does not keep the object that holds the variable alive: when the last reference to the holder is released, the holder terminates and its connection is removed.
- RaiseEvent with no sink connected does nothing. It raises no error.
A WithEvents variable can also hold an object that is not written in twinBASIC, when the project refers to its type library. The object’s own connection point is used, and its outgoing interface is the one its type library lists as a source. The example at the end of the page does this with the sink object of the WMI scripting library.
Example
A class that raises events, a class that listens to them with a WithEvents variable, and a class with no events. The helper module holds the two loops the samples reuse: it finds the first connection point of an object, and counts the sinks connected to a point.
Class Counter
Public Event Changed(ByVal NewValue As Long)
Public Event Finished()
Private Total As Long
Public Sub Increment()
Total += 1
RaiseEvent Changed(Total)
End Sub
Public Sub Finish()
RaiseEvent Finished
End Sub
End Class
Class Display
Public Name As String
Private WithEvents Source As Counter
Public Sub Watch(ByVal Target As Counter)
Set Source = Target
End Sub
Public Sub Unwatch()
Set Source = Nothing
End Sub
Private Sub Source_Changed(ByVal NewValue As Long)
Debug.Print Name & ": " & NewValue
End Sub
End Class
Class Silent
Public Value As Long
End Class
Private Module ConnectionHelpers
Public Function FirstConnectionPoint(ByVal obj As Object) As IConnectionPoint
Dim container As IConnectionPointContainer = obj
Dim points As IEnumConnectionPoints = container.EnumConnectionPoints()
Dim point As IConnectionPoint, fetched As Long
points.Next 1, point, fetched
Return point
End Function
Public Function ConnectionCount(ByVal point As IConnectionPoint) As Long
Dim sinks As IEnumConnections = point.EnumConnections()
Dim item As CONNECTDATA, fetched As Long, n As Long
Do
sinks.Next 1, item, fetched
If fetched = 0 Then Exit Do
n += 1
Set item.pUnk = Nothing
Loop
Return n
End Function
End Module
WithEvents and Set are Advise and Unadvise. The first Increment has no sink, so nothing is printed and the point has no connection. Each Watch adds one, each Unwatch removes one, and the handlers run in the order of the connections:
Dim c As New Counter
Dim point As IConnectionPoint = FirstConnectionPoint(c)
Dim a As New Display, b As New Display
a.Name = "A"
b.Name = "B"
c.Increment
Debug.Print ConnectionCount(point)
a.Watch c
Debug.Print ConnectionCount(point)
b.Watch c
Debug.Print ConnectionCount(point)
c.Increment
a.Unwatch
Debug.Print ConnectionCount(point)
c.Increment
b.Unwatch
Debug.Print ConnectionCount(point)
' Output:
' 0
' 1
' 2
' A: 2
' B: 2
' 1
' B: 3
' 0
A class with events has one connection point, and the container finds it again by the identifier it reports. The enumerator has no second item: its Next raises E_FAIL. A class with no events fails in EnumConnectionPoints with run-time error 445. An identifier that the object does not have, here that of IUnknown, raises CONNECT_E_NOCONNECTION:
Dim c As New Counter
Dim container As IConnectionPointContainer = c
Dim points As IEnumConnectionPoints = container.EnumConnectionPoints()
Dim point As IConnectionPoint, fetched As Long
points.Next 1, point, fetched
Debug.Print fetched ' 1
Dim iid As GUID
point.GetConnectionInterface iid
Dim again As IConnectionPoint = container.FindConnectionPoint(iid)
Debug.Print again Is point ' True
Dim back As IConnectionPointContainer = point.GetConnectionPointContainer()
Debug.Print back Is container ' True
On Error Resume Next
points.Next 1, point, fetched
Debug.Print Hex$(Err.Number) ' 80004005
Err.Clear
Dim other As GUID ' the identifier of IUnknown
other.Data4(0) = &HC0
other.Data4(7) = &H46
container.FindConnectionPoint other
Debug.Print Hex$(Err.Number) ' 80040200
Err.Clear
Dim s As New Silent
Dim silentContainer As IConnectionPointContainer = s
silentContainer.EnumConnectionPoints
Debug.Print Err.Number ' 445
The sink that twinBASIC registers for a WithEvents variable can be taken from EnumConnections and advised by hand. Advising it while it is still connected adds nothing and returns 0. After Unadvise it can be advised again, and the new cookie is a new number. Its IDispatch runs the handler: event 1 of Counter is Changed, and event 2, Finished, has no handler to run. An ordinary object is not a sink, and Unadvise with a cookie nobody holds does nothing:
Dim c As New Counter
Dim point As IConnectionPoint = FirstConnectionPoint(c)
Dim d As New Display
d.Name = "D"
d.Watch c
Dim connections As IEnumConnections = point.EnumConnections()
Dim item As CONNECTDATA, fetched As Long
connections.Next 1, item, fetched
Dim sink As stdole.IUnknown = item.pUnk
Debug.Print item.dwCookie ' 1
Debug.Print point.Advise(sink) ' 0
Debug.Print ConnectionCount(point) ' 1
point.Unadvise item.dwCookie
Debug.Print ConnectionCount(point) ' 0
c.Increment
Dim cookie As Long = point.Advise(sink)
Debug.Print cookie ' 2
c.Increment
Dim target As Object = sink
CallByDispId target, 1, vbMethod, 41
CallByDispId target, 2, vbMethod
point.Unadvise cookie
c.Increment
On Error Resume Next
Dim notASink As New Silent
Dim refused As Long = point.Advise(notASink)
Debug.Print Hex$(Err.Number) ' 80004002
Err.Clear
point.Unadvise 99
Debug.Print Err.Number ' 0
' Output:
' 1
' 0
' 1
' 0
' 2
' D: 2
' D: 41
' 80004002
' 0
A WithEvents variable of a type from a type library works in the same way. This class holds the sink object of the WMI scripting library, a source of events that every Windows installation has. It needs a reference to Microsoft WMI Scripting V1.2 Library in the project:
Class WmiWatcher
Public Completed As Boolean
Private WithEvents Sink As WbemScripting.SWbemSink
Public Sub Attach()
Set Sink = New WbemScripting.SWbemSink
End Sub
Public Sub Detach()
Set Sink = Nothing
End Sub
Private Sub Sink_OnCompleted(ByVal iHResult As WbemScripting.WbemErrorEnum, _
ByVal objWbemErrorObject As WbemScripting.SWbemObject, _
ByVal objWbemAsyncContext As WbemScripting.SWbemNamedValueSet)
Completed = True
End Sub
End Class
After Attach, the sink’s connection point for the outgoing interface ISWbemSinkEvents holds one connection, and after Detach it holds none.
See Also
- IUnknown interface – the base interface and QueryInterface
- IDispatch interface – how a dispinterface sink receives an event
- IEnumVARIANT interface – the enumerator pattern the two enumerators follow
- Event statement – declares an event on a class
- RaiseEvent statement – fires a declared event
- WithEvents statement – connects a sink to a source
- Handles clause – binds a handler without relying on its name
- Interfaces and CoClasses – declaring an interface in twinBASIC