[Previous] [Contents] [Next]

A Simple Connectable Object

To support connection points, Microsoft offers these four interfaces: IConnectionPointContainer, IEnumConnectionPoints, IConnectionPoint, and IEnumConnections. Due to their generic design, these interfaces can initially seem overwhelmingly complex. To make this technology more comprehensible, let's build a simple, pared-down connectable object. Keep in mind that this example is not a full implementation of all the connection point interfaces—we'll develop a full-fledged example later in this chapter.

The example implements the IConnectionPoint and IConnectionPointContainer interfaces. The two other interfaces, IEnumConnectionPoints and IEnumConnections, enumerate the available connection points and the current connections, respectively. While these latter two interfaces are not difficult to implement, enumerators are not simple to understand and they detract from the discussion of connection points, so we'll add the enumerators in the full version of the connection point example.

The Source Interface

As with most other programming tasks in COM+, working with connection points starts in Interface Definition Language (IDL). First, we must define the source interface that will be implemented by the sink and called by the connectable object. For demonstration purposes, we'll define a source interface named IOutGoing, shown below in IDL notation. The GotMessage method of the IOutGoing interface notifies the sink that something has happened in the connectable object.

[ object, uuid(10000005-0000-0000-0000-000000000001),
  oleautomation ]
interface IOutGoing : IUnknown
{
    HRESULT GotMessage(int Message);
}

Next, we need to add this interface to the coclass statement, as shown in the following IDL code. Notice that we declare the IOutGoing interface using the source attribute, as shown in boldface. In the type library created by the Microsoft IDL (MIDL) compiler, this attribute indicates that IOutGoing is a source of events and that the interface is called rather than implemented by the InsideCOM coclass. Every coclass statement in the IDL file can have a default incoming interface (the standard kind) as well as a default source interface. If the default is not indicated using the default attribute, the first outgoing and incoming interfaces are treated as the defaults.1 This assignment of defaults is important because high-level languages such as Microsoft Visual Basic automatically call IUnknown::QueryInterface for the interface marked with the default attribute.

[ uuid(10000003-0000-0000-0000-000000000001),
  helpstring("Inside COM+ Component Type Library"),
  version(1.0) ]
library Component
{
    importlib("stdole32.tlb");

    interface ISum;
    interface IOutGoing;
    [ uuid(10000002-0000-0000-0000-000000000001) ]
    coclass InsideCOM
    {
        interface ISum;
        [source] interface IOutGoing;
    }
};

The IConnectionPoint Interface

A connection point is an object managed by the connectable object that implements the IConnectionPoint interface. The main purpose of this interface is to let a client provide a connectable object with a pointer to the client's sink, as shown in Figure 8-2. Each connection point supports exactly one source interface.

Click to view at full size.

Figure 8-2. A pointer to the client's sink obtained via the IConnectionPoint interface.

Here is the IConnectionPoint interface in IDL notation:

interface IConnectionPoint : IUnknown
{
    // Get the IID of the outgoing interface.
    HRESULT GetConnectionInterface
    (
        [out]   IID * piid
    );

    // Get the IConnectionPointContainer that we belong to.
    HRESULT GetConnectionPointContainer
    (
        [out]   IConnectionPointContainer ** ppCPC
    );

    // Here's a pointer to my sink.
    HRESULT Advise
    (
        [in]    IUnknown * pUnkSink,
        [out]   DWORD *    pdwCookie
    );

    // Don't call us--we'll call you.
    HRESULT Unadvise
    (
        [in]    DWORD dwCookie
    );

    // Who else do you talk to?
    HRESULT EnumConnections
    (
        [out]   IEnumConnections ** ppEnum
    );
}

Of the five methods of IConnectionPoint, we'll initially implement only the two most important ones: Advise and Unadvise. All the other methods simply return E_NOTIMPL for now. The client calls the IConnectionPoint::Advise method to provide the connectable object with a pointer to the client's sink object. In a way, you can consider this call a QueryInterface call in reverse. A client uses the IUnknown::QueryInterface method to discover the interfaces exposed by an object. It calls IConnectionPoint::Advise to provide an object with a pointer to its sink. So instead of the question asked by QueryInterface, "What interfaces do you support?" the Advise method tells the object, "Here is a pointer to my sink."

Although the IConnectionPoint::Advise method provides the connectable object with a pointer to the client sink's IUnknown interface, this pointer alone is not sufficient. To call the methods of the client's sink object, we must obtain a pointer to one of the more interesting interfaces implemented by the sink. Thus, the IUnknown::QueryInterface method is the first call made from the connectable object to the client's sink! Then the IConnectionPoint::Advise method returns a unique number to the client that identifies the advisory relationship that has been established. The client retains this number, called a cookie, for later use in terminating the connection.

A simple implementation of the Advise method looks like this:

HRESULT CInsideCOM::Advise(IUnknown* pUnknown, DWORD* pdwCookie)
{
    // Hard-code the cookie value.
    *pdwCookie = 1;

    // Get a pointer to the sink's source interface.
    return pUnknown->QueryInterface(IID_IOutGoing, 
        (void**)&g_pOutGoing);
}

Since this code is prepared to handle only a single connection, the cookie returned to the client is a dummy placeholder. The connectable object then calls the sink object's IUnknown::QueryInterface method to obtain a pointer to its IOutGoing interface. This pointer is stored in a global variable for later use. Conveniently, the IUnknown::AddRef operation required to increment the reference counter is encapsulated within the QueryInterface call.

The QueryInterface call might seem redundant. After all, since each connection point supports one source interface, why doesn't the client simply provide that interface pointer in the Advise call in place of the IUnknown pointer? The answer is that connection points are designed to be a general-purpose mechanism for configuring bidirectional communication in COM+. Because the designers of the connection point interfaces had no way of knowing what custom interfaces a sink object might implement, IUnknown was the only option.

The IConnectionPoint::Unadvise method terminates an advisory relationship that was previously established using the Advise method. The cookie argument passed to Unadvise identifies which connection should be terminated. In the following implementation, the Unadvise method simply releases the interface pointer obtained previously:

HRESULT CInsideCOM::Unadvise(DWORD dwCookie)
{
    // Ignoring dwCookie; only one connection is 
    // supported anyway.
    g_pOutGoing->Release();
    return NOERROR;
}

At this stage, the component has a pointer to the sink object and can call the GotMessage method of the IOutGoing interface. An interesting question remains, however: how does the client get a pointer to the IConnectionPoint interface in the first place? This is the job of the IConnectionPointContainer interface.

The IConnectionPointContainer Interface

The methods of the IConnectionPoint interface establish and release connections between a client and a connectable object; the methods of the IConnectionPointContainer interface discover the connection points of a connectable object. When implemented by an object, the IConnectionPointContainer interface embodies the qualities that make an object "connectable." The two methods of the IConnectionPointContainer interface are shown here:

interface IConnectionPointContainer : IUnknown
{
    // Tell me about your connection points.
    HRESULT EnumConnectionPoints
    (
        [out] IEnumConnectionPoints ** ppEnum
    );

    // Do you support a specific connection point?
    HRESULT FindConnectionPoint
    (
        [in] REFIID riid,
        [out] IConnectionPoint ** ppCP
    );
}

Clients that want to enumerate all the source interfaces supported by a connectable object should call IConnectionPointContainer::EnumConnectionPoints, which retrieves a pointer to an object that supports the IEnumConnectionPoints enumeration interface. At this stage, we do not support enumerator objects, so this method returns E_NOTIMPL. Note that returning E_NOTIMPL from IConnectionPointContainer::EnumConnectionPoints is not recommended because with the exception of type information, a client cannot determine the interface identifiers (IIDs) of the source interfaces that a connectable object supports.

The IConnectionPointContainer::FindConnectionPoint method is the QueryInterface of connection points. It asks the connectable object, "Do you support a particular source interface?" If the connectable object supports the requested interface, the method returns a pointer to that source interface's connection point (an object that implements the IConnectionPoint interface). QueryInterface actually obtains the IConnectionPoint interface pointer, as shown in the following implementation of the IConnectionPointContainer::FindConnectionPoint method:

HRESULT CInsideCOM::FindConnectionPoint(REFIID riid, 
    IConnectionPoint** ppCP)
{
    // If we support the source interface, then QI for 
    // the connection point.
    if(riid == IID_IOutGoing)
        return QueryInterface(IID_IConnectionPoint, 
            (void**)ppCP);
    return E_NOINTERFACE;
}

This implementation of FindConnectionPoint supports only one source interface, IOutGoing. E_NOINTERFACE is returned if the client is looking for the connection point of some other source interface. Assuming that the client wants IOutGoing, QueryInterface is called to obtain a pointer to the connection point, and that pointer is returned to the client. This answers the question of how the client obtains the pointer to the IConnectionPoint interface. If you're wondering how the IConnectionPointContainer interface is obtained, just remember your good friend QueryInterface. The InsideCOM coclass implements QueryInterface in the standard fashion, simply handing out pointers to the IConnectionPointContainer interface as requested, as shown below in boldface:

HRESULT CInsideCOM::QueryInterface(REFIID riid, void** ppv)
{
    if(riid == IID_IUnknown)
        *ppv = (ISum*)this;
    else if(riid == IID_ISum)
        *ppv = (ISum*)this;
    else if(riid == IID_IConnectionPointContainer)
        *ppv = (IConnectionPointContainer*)this;
    else if(riid == IID_IConnectionPoint)
        *ppv = (IConnectionPoint*)this;
    else 
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

The final bit of code required in the component is the call to the IOutGoing::GotMessage method. To make things interesting, we'll call this method whenever the user presses a key on the keyboard. The ASCII key code is sent to the client in the Message parameter of GotMessage. To make things even more interesting, you can configure the client and component on different computers and then watch the events fire across the network. The following code reads keyboard input from the console by using the Win32 function ReadConsoleInput (shown in boldface) and then calls the GotMessage method of the sink object:

// Press any key to fire an event at the client.
HANDLE handles[2] = 
    { g_hEvent, GetStdHandle(STD_INPUT_HANDLE) };

// READY...
while(WaitForMultipleObjects(2, handles, FALSE, INFINITE) _ 
    WAIT_OBJECT_0 == 1)
{
    INPUT_RECORD ir;
    DWORD read;

// AIM...
    ReadConsoleInput(handles[1], &ir, 1, &read);
    if(ir.EventType == KEY_EVENT && 
        ir.Event.KeyEvent.bKeyDown == TRUE)

// FIRE!!!
        g_pOutGoing->
            GotMessage(ir.Event.KeyEvent.uChar.AsciiChar);
}

Implementing a Sink in C++

To test the connectable object, we must expand the client program built in the previous chapters to include a sink object that receives and responds to the fired events. The client code is where the IOutGoing interface is actually implemented by the CSink class, as shown here:

class CSink : public IOutGoing
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

    // IOutGoing
    HRESULT __stdcall GotMessage(int Message);

    CSink() : m_cRef(0) { }
    ~CSink() { }

private:
    long m_cRef;
};

As you can see, the CSink class implements the IUnknown and IOutGoing interfaces. We won't bore you with the all-too-standard implementations of IUnknown::AddRef, Release, and QueryInterface. The IOutGoing::GotMessage method, shown here, is more interesting because the connectable object calls it whenever the user presses a key:2

HRESULT CSink::GotMessage(int Message)
{
    if(Message == (int)'b' || Message == (int)'B')
        PlaySound("BrockschmidtQuack", NULL, 
            SND_RESOURCE|SND_ASYNC);
    cout << "CSink::GotMessage is " << (char)Message << endl;
    return S_OK;
}

Setting Up a Connection Point

The client code starts out in the standard fashion, calling CoCreateInstance to instantiate the InsideCOM coclass, as shown here:

// Instantiate the object.
IUnknown* pUnknown;
CoCreateInstance(CLSID_InsideCOM, NULL, CLSCTX_LOCAL_SERVER, 
    IID_IUnknown, (void**)&pUnknown);

Next, the client calls IUnknown::QueryInterface to determine whether InsideCOM is a connectable object, as shown in the following code. If the object supports the IConnectionPointContainer interface, it qualifies as "connectable."

// Query for IConnectionPointContainer.
IConnectionPointContainer* pConnectionPointContainer;
hr = pUnknown->QueryInterface(IID_IConnectionPointContainer, 
    (void**)&pConnectionPointContainer);

The IConnectionPointContainer::FindConnectionPoint method is then called to retrieve the connection point for the IOutGoing interface, as shown below. Notice that we are not yet using the IConnectionPointContainer::EnumConnectionPoints method because this method currently returns E_NOTIMPL.

// Find the connection point for IOutGoing.
IConnectionPoint* pConnectionPoint;
hr = pConnectionPointContainer->FindConnectionPoint(
    IID_IOutGoing, &pConnectionPoint);

Using the pointer obtained from FindConnectionPoint, the client calls IConnectionPoint::Advise to provide the connectable object with a pointer to the sink object. Of course, the sink object must be instantiated in the client before its pointer can be sent to the component! This task is accomplished using the C++ keyword new, as shown here:

// Instantiate the sink object.
CSink* mySink = new CSink;

// Give the connectable object a pointer to the sink.
DWORD dwCookie;
pConnectionPoint->Advise((IUnknown*)mySink, &dwCookie);

At this stage, there is an advisory relationship between the client's sink and the connectable object. Whenever the user presses a key from within the component, the sink's IOutGoing::GotMessage method is called. When the user tires of this fantastic program and decides to exit, the IConnectionPoint::Unadvise method is called to terminate this connection, as shown here:

// Terminate the connection.
pConnectionPoint->Unadvise(dwCookie);

This call is followed by several calls to IUnknown::Release to free all the pointers used in the course of this exercise, as shown in the following code snippet:

// Release everything in sight.
pConnectionPoint->Release();
pConnectionPointContainer->Release();
pUnknown->Release();

Figure 8-3 shows the calls made by the client and the connectable object to establish their relationship; the numbers indicate the order of the calls.

Click to view at full size.

Figure 8-3. The steps that the client and the connectable object take so the InsideCOM connectable object can obtain a pointer to its client's sink and fire back events.

Multicasting with Connection Points

A one-to-one relationship is typical but not required between a client and a connectable object. The connection point architecture is generic enough to support a connectable object that fires events at multiple sinks, or a sink that gets hooked up to several connectable objects. Figure 8-4 shows several clients connecting their sinks to a single connectable object.

Figure 8-4. A connectable object firing events at multiple sinks.

Although the connection point architecture supports multicasting, this doesn't happen automatically. The connectable object itself must keep track of its client sinks and then fire the events individually. It can sequentially fire the same event at all the client sinks that called IConnectionPoint::Advise by iterating through its current connections using the following code:

IOutGoing* pOutGoing;
for(int count = 0; count < CCONNMAX; count++)
    if(m_rgpUnknown[count] != NULL)
    {
        pOutGoing = (IOutGoing*)m_rgpUnknown[count];
        pOutGoing->SomethingHappened(WhatHappened);
    }

Figure 8-5 shows the inverse of this topology: a single client application has advised several connectable objects of its sink. Now the same client sink receives events fired by any of the connectable objects. This setup is most useful when a client wants to listen to several connectable objects at the same time in one sink.

Figure 8-5. A single sink receiving events fired from multiple connectable objects.

A Visual Basic Sink

To liven things up a bit, let's use Visual Basic to test our simple connectable object. After slogging through the preceding CSink code, you'll find that intercepting events in Visual Basic is almost dishearteningly easy. You simply use the WithEvents keyword when you declare an object variable, and Visual Basic dynamically creates a sink object that implements the source interface supported by the connectable object. Then you instantiate the object using the Visual Basic New keyword. Now, whenever the connectable object calls methods of the source interface, Visual Basic's sink object checks to see whether you have written any code to handle the call. For example, in the following code, the standard-looking myRef_GotMessage event procedure handles the InsideCOM coclass's GotMessage event:

' Declare the object variable.
Dim WithEvents myRef As InsideCOM

Private Sub Form_Load()
    ' Instantiate the connectable object.
    Set myRef = New InsideCOM
End Sub

' Catch events here...
Private Sub myRef_GotMessage(ByVal Message As Long)
    Print Chr(Message)
End Sub

The rules of COM+ state that the InsideCOM coclass is not an entirely legitimate connectable object because it does not implement all the connection point interfaces. But since Visual Basic is willing to work with it, it has a great deal of credibility.3 If you have only a relatively basic need for a connectable object, this code might be sufficient. Connectable objects were designed for ActiveX controls and thus can seem unnecessarily complicated if the full power of this paradigm is not required.

Creating a Sink Dynamically

This simple Visual Basic code raises an interesting question: How does Visual Basic's sink implement the IOutGoing interface? In Figure 8-3, the sink object exposes the IOutGoing interface, which is easy enough to accomplish because the code we wrote in the CSink class inherited the interface definition and implemented its methods. Visual Basic, however, has no knowledge of the IOutGoing interface, but it can still respond correctly to the IOutGoing::GotMessage event.

This fact is as amazing as it seems. For Visual Basic to correctly respond to events fired from connectable objects, it must dynamically synthesize a sink object that implements the designated source interface. This means that at run time, Visual Basic must create a v-table containing entry points for each member function, and later it must call the Visual Basic procedure that handles the event (if one exists), properly constructing a stack frame on the way.

Figure 8-6 shows the steps that Visual Basic takes to perform this miraculous feat. First, it obtains the component's type information4 to learn about the source interface supported by the connectable object. Using this information about the interface, its methods, and their arguments, Visual Basic dynamically constructs a v-table for the source interface. Now, when the connectable object fires an event at Visual Basic, everything works properly. The connectable object is not aware of the extraordinary lengths to which Visual Basic has gone to make this work.

Click to view at full size.

Figure 8-6. The steps that Visual Basic takes to handle events fired from a connectable object.

The problem with creating a generic sink, such as that provided by Visual Basic, is that at compile time the methods of the source interfaces supported by the connectable object are unknown. C++ does not help solve this problem because a C++ compiler adds only virtual functions that are known at compile time to a v-table. To dynamically create a v-table, as Visual Basic does, you must drop below the level of functionality offered by virtual functions in C++ and handcraft the v-table structure normally generated automatically by the C++ compiler. You can define the sink object using a structure, which in C++ is equivalent to a class with public members anyway.

In the following declaration, you can see the binary standard that defines all COM+ objects. The first entry in the memory structure of a COM+ object must always be a pointer to the v-table. Normally, this entry is created automatically and hidden from view by C++ for any class containing virtual functions.

// This sink can implement any interface at run time.
// In C++, struct == public class.
struct CStink // Class for the generic sink object
{
    // The first item must be a pointer to the v-table 
    // of the sink, declared as a pointer to one or more 
    // function pointers.
    // Here we can see the binary standard that defines COM+.
    void (__stdcall **pVtbl)();

    // Next comes the object's data.
    long m_cRef; // Reference-counting variable
};

Since IUnknown is the only interface that a COM+ object is required to implement, it is the only interface that can be predefined for a truly generic sink object. Because all three IUnknown methods are no longer members of a C++ class, they take a first parameter that points to the current object. This parameter replaces the implicit C++ this pointer that is normally available in all nonstatic member functions. Because all C++ compilers provide this argument automatically, it is a de facto part of the binary COM+ standard. In the following code, the first argument is named me to avoid collision with the this keyword. Also notice that any access to the member variable m_cRef is done through the me pointer. A little bit of this type of code can quickly make you appreciate the work normally done automatically by C++.

// Nonstatic methods in a class always have an implicit this 
// argument that points to the current object. Since
// these methods are not part of a class, we make believe....
// Every method in the interface must pretend to have a this 
// argument because all callers will provide it.
ULONG __stdcall CStink_AddRef(CStink* me)
{
    // me is a stand-in for this, a reserved keyword in C++.
    // Everything must be explicitly accessed through the 
    // pointer to the object.
    // Now we can appreciate what C++ does automatically.
    return ++me->m_cRef;
}

ULONG __stdcall CStink_Release(CStink* me)
{
    if(--me->m_cRef != 0)
        return me->m_cRef;
    delete me; // We can even use delete.
    return 0;
}

HRESULT __stdcall CStink_QueryInterface(CStink* me, REFIID riid, 
    void** ppv)
{
    if(riid == IID_IUnknown || riid == IID_IOutGoing)
        *ppv = &me->pVtbl; // Here's a pointer to our interface.
    else 
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    CStink_AddRef(me);
    return S_OK;
}

When the time comes to instantiate the sink object, you can use the standard C++ new operator, as shown in the following code fragment. The result does not qualify as a COM+ object, however, because it does not yet support the IUnknown methods.

// Instantiate the object, sort of...
CStink* mySink = new CStink;

Now you allocate memory for a v-table and initialize the individual function pointers. The following code uses the COM+ memory allocation function CoTaskMemAlloc. This function automatically gets a pointer to the system's implementation of the memory allocation interface IMalloc and then allocates memory by calling IMalloc::Alloc. The size of the v-table allocated depends on the number of methods in the source interface being implemented. The minimum size is always 12 bytes: 4 bytes for each of the three methods of IUnknown. Since the IOutGoing interface defines the one additional GotMessage method, four v-table entries are required, consuming a grand total of 16 bytes.

// 3 methods in IUnknown + number of methods in the 
// source interface
int num_methods = 4;

// Allocate memory for the v-table, and cast the returned 
// void* to a pointer to an array of function pointers.
void (__stdcall **IStink)() = 
    (void (__stdcall **)())CoTaskMemAlloc(
        num_methods * sizeof(void*));
    // sizeof(void*) = 4 bytes per pointer.

Now you must initialize each entry in the v-table so that it points to the correct function, as shown in the following code. Note that the order of these functions is not arbitrary. The order in which an interface's methods appear in the v-table is a defined part of every interface. For IUnknown, the order is QueryInterface, AddRef, and then Release.

// Initialize the v-table to point to our implementations.
// These three methods always come first (and in this order)!
*(IStink + 0) = (void (__stdcall *)())CStink_QueryInterface;
*(IStink + 1) = (void (__stdcall *)())CStink_AddRef;
*(IStink + 2) = (void (__stdcall *)())CStink_Release;

At this stage, the IUnknown functionality is in place. Now a truly generic implementation of a sink object, such as that provided by Visual Basic, gets the type information for the connectable object's source interface. Additional functions can be synthesized once the sink has learned about all the methods (and their associated arguments) of the source interface. How to accomplish this task is a bit beyond the scope of this section, so we'll stick with simply wiring the last v-table entry to point to an implementation of the IOutGoing::GotMessage method discussed earlier.

// Now add any additional methods to the v-table based on the 
// available type information for the source interface.

// IOutGoing has only one additional method.
*(IStink + 3) = (void (__stdcall *)())GotMessage;

Finally, the v-table structure must be plugged into the sink object itself, and its reference counter must be initialized, as shown here:

// Give the sink a brain.
mySink->pVtbl = IStink;

// Initialize the sink's reference counter.
mySink->m_cRef = 0;

Now a pointer to the sink object can be passed to the connectable object using the standard IConnectionPoint::Advise method, as shown in the following code. To the connectable object, our handcrafted sink should be indistinguishable from one mass-produced by C++. You can test this by running the client with the connectable object we built earlier in this chapter in the section titled "A Simple Connectable Object."

DWORD dwCookie;
hr = pConnectionPoint->Advise((IUnknown*)mySink, &dwCookie);

After IConnectionPoint::Unadvise is called to terminate the connection, the v-table we worked so hard to create must be destroyed. Here the CoTaskMemFree function, the analogue of CoTaskMemAlloc, does the job:

// Now free the v-table; the object has probably already 
// deleted itself.
CoTaskMemFree(IStink);

You can implement unfamiliar source interfaces in a sink only if type information describing the outgoing interface is available. Through type information, Visual Basic learns about all the attributes, methods, and method arguments of an interface—enough information to correctly generate a sink object that implements the interface and to connect that sink with the connectable object. Without this information, a client that encounters an unfamiliar outgoing interface is completely out of luck. In fact, the idea of type information was originally developed in conjunction with connectable objects for the ActiveX Controls specification (then called OLE Controls). Fortunately, most clients need not be as generic as Visual Basic and thus know at compile time what source interfaces must be implemented by their sink.

Visual Basic and high-level scripting languages such as VBScript do have one limitation relating to connection points: they do not support nondefault source interfaces. For example, the following IDL code defines a coclass containing two source interfaces, IOutGoing and INonDefaultOutGoing, shown in boldface. Currently Visual Basic and scripting languages ignore the INonDefaultOutGoing interface, restricting you to events of the default source interface. Of course, client applications written in C++ or Java can access any source interface defined by a connectable object.

[ uuid(10000003-0000-0000-0000-000000000001) ]
library Component
{
    importlib("stdole32.tlb");

    interface IOutGoing;
    interface INonDefaultOutGoing;

    [ uuid(10000002-0000-0000-0000-000000000001) ]
    coclass InsideCOM
    {
        [default, source] interface IOutGoing;
        [source] interface INonDefaultOutGoing;
    }
};

A Java Sink

A program written in Java can also intercept events fired by a connectable object. Implementing a sink in Java is nearly as simple as in Visual Basic. First, you use the Project/Add COM Wrapper command in Microsoft Visual J++ and select the Inside COM+ Component Type Library. Visual J++ creates a subdirectory named component and then generates Java wrappers for the coclasses and interfaces described in the type library of the component: InsideCOM.java, ISum.java, and IOutGoing.java.

The JavaSink class shown in Listing 8-1 implements the GotMessage method of the IOutGoing interface. Hooking up the sink to the connectable object is quite easy using the com.ms.com.ConnectionPointCookie class provided with Microsoft's Java Virtual Machine (VM). The constructor of the ConnectionPointCookie class creates a connection between the connectable object and the sink using the IID of the source interface. Notice the use of the import statement to access the classes and interfaces of the component package. This reference lets you access the classes and interfaces of the component package by prefixing each reference with component.

JavaSink.java


import component.*;
import com.ms.com.ConnectionPointCookie;

public class JavaSink implements IOutGoing
{
    public void GotMessage(int Message)
    {
        System.out.println((char)Message);
    }
}

class Driver
{
    public static void main(String[] args)
    {
        ISum ref = (ISum)new component.InsideCOM();
        System.out.println("8 + 9 = " + ref.Sum(8, 9));
        
        try
        {
            JavaSink j = new JavaSink();
            ConnectionPointCookie EventCookie = new 
                ConnectionPointCookie(ref, j, 
                Class.forName("component.IOutGoing"));
            System.out.println("Press Enter to exit.");
            System.in.read();
        }
        catch(Exception e)
        {
        }
    }
}

Listing 8-1. A Java client that implements a sink that can handle GotMessage events fired from the InsideCOM connectable object.