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

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:

  1. The source supports IConnectionPointContainer, so the client asks it for that interface with QueryInterface.
  2. 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.
  3. 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.
  4. When an event occurs, the source calls the matching method of every connected sink, in the order the connections were made.
  5. 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 names CONNECT_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