Click to Rate and Give Feedback
Related Articles

As you move forward with your use of ADO. NET, you'll need to know how to approach situations that you previously learned to handle in ADO and now have to tackle with ADO. NET. Just as n-tiered solutions developed using Visual Basic®, C++, and ASP often rely on ADO for their data access needs, Windows® Forms, Web Forms, and Web services rely on ADO.

John Papa

MSDN Magazine August 2004

...

Read more!

Because Virtual Server is built upon a set of COM modules, you can automate the creation and testing of virtual machines. Here we use Windows PowerShell to run the tests.

Dr. James McCaffrey and Paul Despe

MSDN Magazine December 2008

...

Read more!

Whether you're storing database connection strings, user credentials, or logon info, you'll need to practice good defensive programming techniques to avoid those surprise situations in which your data is exposed. In this article, author Kenny Kerry shows you how.

Kenny Kerr

MSDN Magazine November 2004

...

Read more!

In recent years ADO has taken the lead as the preferred method for implementing data access in Windows®-based applications. Huge numbers of ADO applications are in use today and many developers are well versed in ADO development.

John Papa

MSDN Magazine July 2004

...

Read more!

Reflection is useful for debugging and logging and otherwise providing the type information you need. Here you’ll see how to use reflection on COM types.

Lucian Wischik

MSDN Magazine January 2009

...

Read more!

Also by this Author

The CLR allows seamless interactions between Microsoft .NET applications and COM. But how, exactly? The CLR team knows.

Thottam R. Sriram

MSDN Magazine January 2007

...

Read more!

Popular Articles

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Writing a Web application with ASP.NET is unbelievably easy. So many developers don't take the time to structure their applications for great performance. In this article, the author presents 10 tips for writing high-performance Web apps. The discussion is not limited to ASP.NET applications because they are just one subset of Web applications.

Rob Howard

MSDN Magazine January 2005

...

Read more!

Here we introduce you to some of the concepts behind the new F# language, which combines elements of functional and object-oriented .NET languages. We then help you get started writing some simple programs.

Ted Neward

MSDN Magazine Launch 2008

...

Read more!

Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

Josh Smith

MSDN Magazine July 2008

...

Read more!

The MVP pattern helps you separate your logic and keep your UI layer free of clutter. This month learn how.

Jean-Paul Boodhoo

MSDN Magazine August 2006

...

Read more!

CLR Inside Out
COM Connection Points
Thottam R. Sriram

Code download available at: CLRInsideOut2007_09.exe (252 KB)
Browse the Code Online
Atypical scenario in COM has client objects instantiating server objects and then making calls to those objects. Without a special mechanism, however, it would be very difficult for those server objects to turn around and make calls back to the client objects. COM connection points provide this special mechanism, enabling two-way communication between the server and client. Using connection points, the server can call the client when certain events happen on the server.
With connection points, the server specifies the events that it is capable of raising by defining an interface. Clients that have actions to be taken when these events are raised on the server register themselves with the server. The clients subsequently provide the implementation for the interface defined by the server.
There are standard mechanisms through which clients can register themselves with the server. COM provides IConnectionPointContainer and IConnectionPoint interfaces for this.
The clients for a COM connection point server can be written in C++ and in C# managed code. The C++ client registers an instance of a class that provides the implementation for the sink interface. The managed client registers delegates for individual events, thus creating a single sink per event notification method. In the managed world, there are two ways by which the client can register itself—I detail both of these methods later in this column.
There are very few working samples for eventing and interop on the Web. In this column, I focus on creating an Active Template Library (ATL) connection point server. This involves exposing a COM method, defining an event interface that will be implemented by the client, and implementing the code that raises the events from the server. I also show you a sample C++ client that provides an implementation of the sink plus a sample C# client and the two ways you can register and listen to events from the server. Finally, I talk about the recommended way to implement a managed event sink.

The Sample Scenario
In my scenario, the server exposes a COM method:
HRESULT Add(int nFirst, int nSecond)
The server also defines ConnectionPointContainer and connection points so that clients can register with it. In addition, the server defines an interface, _IAddEvents, which has two methods in it:
HRESULT AdditionStarted()
HRESULT AdditionCompleted(int nResult)
The client provides the implementation for _IAddEvents and invokes the Add method on the server. The server fires the AdditionStarted and AdditionCompleted methods on the client to notify it appropriately. Then the client performs the appropriate actions associated with these events.

Creating a Connection Point on a COM Server
In the January 2007 installment of CLR Inside Out, I described in detail how to create a simple ATL COM server (see msdn.microsoft.com/msdnmag/issues/07/01/CLRInsideOut). Today's column assumes you've already gone through the process of creating an ATL COM server called ATLConnectionPointServer. So if you haven't done so, you may want to read the earlier column before proceeding.
So now you need to define a COM interface implemented by the server and make it a connection point. The process of creating a connection point on top of this COM server is straightforward. To do this, open the Class View in Visual Studio® and create a simple ATL object. Just right-click on ATLConnectionPointServer and add a class, select a simple ATL object, and provide the name of the class as Add. Be sure to select Supports: Connection Points when you walk through the wizard.
Now you have a server interface IAdd that can be called from the client. If you build the server, you will notice that there are two interfaces defined here. One is IAdd, which implements IDispatch, and the other is the dispinterface _IAddEvents.
Next add a new method, called Add, to the interface IAdd. This takes in two integers and returns an HRESULT. To do this, right-click on IAdd and select Add Methods. The signature of the methods will be:
HRESULT Add([in] int nFirst, [in] int nSecond)
Now open ATLConnectionPointServer.idl and add methods AdditionStarted and AdditionCompleted to the _IAddEvents interface as shown in Figure 1.
library ATLConnectionPointServerLib
{
    importlib("stdole2.tlb");
    [
        uuid(7F45FEA6-4D7C-489C-A852-19BA8B29D8AB),
        helpstring("_IAddEvents Interface")
    ]
    dispinterface _IAddEvents
    {
        properties:
        methods:
        [id(1), helpstring("AdditionStarted")]HRESULT AdditionStarted();
        [id(2), helpstring("AdditionStarted")]
            HRESULT AdditionCompleted(int nResult);
    };
    [
        uuid(15B6C26A-0416-4C8F-9533-89F318355E31),
        helpstring("Add Class")
    ]
    coclass Add
    {
        [default] interface IAdd;
        [default, source] dispinterface _IAddEvents;
    };
};

If you compile the project at this point, you'll notice an autogenerated file called _IAddEvents_CP.h. This file, which is generated by ATL, contains an empty CProxy_IAddEvents class. This is the class that provides the firing of the events once the connection point is complete and hooked up.
Go to Class View and right-click on CAdd then select Add | Add Connection Point. In the following wizard, select _IAddEvents. If you open the _IAddEvents_CP.h file now, it will contain auto-generated code for two methods, namely Fire_AdditionStarted and Fire_AdditionCompleted. This is the code that calls back to the client sink objects when they register with the server.
You are now close to completing the implementation of the server. All you have remaining is the implementation of the Add method on the server and the trigger points for firing events from the server.
Open Add.cpp and provide an implementation for the Add method that you added. The implementation looks like this:
STDMETHODIMP CAdd::Add(int nFirst, int nSecond)
{
    // Fire AdditionStarted event
    Fire_AdditionStarted();

    int nResult = nFirst + nSecond;
    Sleep(1000); // simulate the addition taking a long time

    // Fire AdditionCompleted event
    Fire_AdditionCompleted(nResult);
    return S_OK;
}
Now just compile the solution and your server is ready.

The Client
Now you can move onto the client. I will start by looking at a C++ client and then I'll move on to a managed client.
The client is responsible for five main tasks:
  • It must provide you with the implementation for the _IAddEvents interface.
  • It must provide an interface pointer to the Add interface on the server.
  • It must get the ConnectionPoint of ConnectionPoinContainer of the Add interface and add the sink interface.
  • It must call the Add method and waits for the events to be fired from the server.
  • It must close cleanly and exit.
To implement the client, open a new C++ project called ConnectionPointClient and add a new C++ source file to the project. Add the ATLConnectionPointServer.h and ATLBase.h file to the project. The sink implements the _IAddEvents that the server has defined. There are two methods in this interface: AdditionStarted and AdditionCompleted. An implementation for these two methods is shown in Figure 2.
class CSink : _IAddEvents
{
    private:
    DWORD       m_dwRefCount;
    public:

    CSink::CSink() {m_dwRefCount = 0;}
    CSink::~CSink()    {}

    HRESULT STDMETHODCALLTYPE AdditionStarted()
    {
        printf("C++ SINK: Addition started event fired ... \n");
        return S_OK;
    };

    HRESULT STDMETHODCALLTYPE AdditionCompleted(int nResult)
    {
        printf("C++ SINK: Addition completed event fired ... \n");
        printf("C++ SINK: Addition result: %d \n",nResult);
        return S_OK;
    };
...

For simplicity, I have implemented the dispinterface on the client; the sample code provides an ATL client that does this automatically. The sink in this implementation merely prints the fact that it has been called and the result when the addition is completed. Your sink is now implemented and ready to go.

Get the Add and ConnectionPoint Interface
Now that the sink has been implemented, let's work on the client that will register this sink with the server. The client handles three main tasks.
  • It gets the interface pointer to the server Add interface.
  • It gets the ConnectionPoint of ConnectionPointContainer from the Add interface.
  • It registers the Sink interface with the server.
First, you get the interface IAdd to the server as follows:
CoInitialize(NULL);
    
hr = CoCreateInstance(
    CLSID_Add, NULL, CLSCTX_ALL, IID_IAdd, (void **)&pAdd);
if(hr != S_OK) { return; }
Next, you must get the connection point on the server so you can register the sink implementation with it. To do this, get the ConnectionpointContainer from the IAdd interface as follows:
// Using the interface for add, 
// query for IConnectionPointContainer interface
hr = pAdd->QueryInterface(
    IID_IConnectionPointContainer,(void **)&pCPC);
if ( !SUCCEEDED(hr) ) { return; }
Now, you can get to the ConnectionPoint:
// Using the IConnectionPointContainer, 
// get the IConnectionPoint interface
hr = pCPC->FindConnectionPoint(DIID__IAddEvents,&pCP);
if ( !SUCCEEDED(hr) ) { return; }
The client now has to create an instance of its sink implementation and register it with the server. To do this, the client creates an instance of the sink and gets its IUnknown interface pointer as follows:
// Create an instance of the sink object to pass 
// to the server
pSink = new CSink();
if ( NULL == pSink ) { return; }

// Get the interface pointer to CSink's IUnknown pointer, which you
// will pass to the server
hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);
if(!SUCCEEDED(hr)) { return; }
You are close to completing the client. All that remains is to register the sink with the server, call the server, and clean up. The client registers the instance of the sink with the server:
// Pass the sink interface to the server through the Advise
hr = pCP->Advise(pSinkUnk,&dwAdvise); 
if(!SUCCEEDED(hr)) { return; }
You have now registered the client sink interface with the server.
The client calls the Add method on the server and passes it two arguments as required by the method. The result of the addition is returned through the AdditionCompleted event, not directly from the Add call. Now call the Add method on the IAdd interface pointer you obtained:
pAdd->Add(1, 5);
This call should trigger the firing of the events which in turn should call into the client. At this point, you can clean up the client by releasing all the interfaces you've obtained (see Figure 3).
// Release the IConnectionPointContainer interface. 
if(pCPC != NULL) pCPC->Release();

// Unadvise the event call back we registered.
if(pCP != NULL) { pCP->Unadvise(dwAdvise); } 

if(pSinkUnk != NULL) { pSinkUnk->Release(); }

// Disconnect from the server.
if(pCP != NULL) { pCP->Release(); }
 
// Release interfaces.
if(pAdd != NULL) { pAdd->Release(); }

CoUninitialize();
return;

You're finally done with your client. You can now compile the client and execute it:
cl COMConnectionPointClient.cpp
On execution, you should see the following output:
C++ SINK: Addition started event fired ...
C++ SINK: Addition completed event fired ...
C++ SINK: Addition result: 6

Managed Client
Now I want to discuss using the same ConnectionPointServer from managed code. The managed client is much simpler than the COM client. There are two ways you can implement the client. First, I'll focus on the recommended approach.
To begin, import the server DLL to managed code to obtain ATLConnectionPointServerLib.dll by using the Microsoft® .NET Framework Type Library to Assembly Converter tool, tlbimp.exe, running the following command:
tlbimp ATLConnectionPointServer.dll
You need to reference the resulting assembly in your managed project and then provide an implementation for the sink interface on the client, such as the one shown in Figure 4. The ManagedSink class implements two methods, AdditionStarted and AdditionCompleted, as defined in the _IAddEvents interface. With that, your sink event handler is now complete and ready. (Seems almost too simple compared to the COM client, doesn't it?)
public class ManagedSink :_IAddEvents
{
    public void AdditionStarted()
    {
        Console.WriteLine("C# SINK: Addition started event fired ...");
    }

    public void AdditionCompleted(int nResult)
    {
        Console.WriteLine("C# SINK: Addition completed event fired ...");
        Console.WriteLine("C# SINK: Addition result: {0}", nResult);
        return;
    }
};

As with the COM client, you have to register the sink with the server so the server can invoke the sink when firing the events. There are, however, some differences in the way the managed client registers itself with the server.
The COM client registered the instance of its sink object that implemented the interface _IAddEvents with the server. As a reminder, the following call registered the COM client:
// Pass the sink interface to the server through the Advise
hr = pCP->Advise(pSinkUnk,&dwAdvise); 
if(!SUCCEEDED(hr)) { return;}
With the managed client, you register individual methods as delegates with the server. To accomplish this, you create an instance of the sink object:
ManagedSink ms = new ManagedSink();
Create an instance of the server object and add the AdditionStarted and AdditionCompleted event handlers separately, like this:
AddClass a = new AddClass();
a.AdditionStarted += ms.AdditionStarted;
a.AdditionCompleted += ms.AdditionCompleted;
The client registers two different interfaces for each of the event handlers with the server. The previous calls to add the delegates on the client add a ref count on the Runtime Callable Wrapper (RCW) on the client. The ref count has to be released by removing the event handler once the call is completed, like so:
a.Add(1, 5);
a.AdditionStarted -= ms.AdditionStarted;
a.AdditionCompleted -= ms.AdditionCompleted;
Finally, compile ManagedClient.cs:
csc /r:ATLConnectionPointServerLib.dll ManagedClient.cs 
And run the executable. You should see the following output:
C# SINK: Addition started event fired ...
C# SINK: Addition completed event fired ...
C# SINK: Addition result: 6

Wrap-Up
Writing a client implementation for dispinterface in ATL can be slightly complicated. The sample I've discussed here deliberately works around the complexities by having its own Invoke implementation.
I want to thank Cosmin Radu, Ladi Prosek, Mason Bendixen, Varun Sekhri, and Claudio Caldato for all their help and suggestions in making this column happen.

Send your questions and comments to clrinout@microsoft.com.


Thottam R. Sriram has a master's degree in computer science from Concordia University, Montreal, Canada. He is currently a Program Manager on the CLR team working on COM interop. Before joining the CLR team, Thottam worked with the Windows team. Visit his blog at blogs.msdn.com/thottams.

Page view tracker