|
The following source was built using Visual Studio 6.0 SP5 and Visual Studio .Net. You need to have the
Microsoft NetMeeting SDK installed. This is available from here
or as part of recent versions of the Microsoft Platform SDK.
Sinking connection points in C++ objects.
Many COM objects provide events to their clients by means on Connection Points. A Connection Point is a generalised
method of allowing clients to register a COM interface so that the object can notify them of things that they may be
interested in. The problem is, sometimes you will be developing code that simply uses COM objects and doesn't implement
any COM objects itself, yet you still need to be able to register a COM object to
receive notifications via
a Connection Point. This article shows you how you can use Connection Points and sink events from them without needing
to use ATL or MFC or expose real COM objects from your application.
Registering for notifications
Suppose we want to use a COM object that provides notifications via Connection Points, the NetMeeting Conference
Manager object for example. Our application will create an instance of the Conference Manager object and manipulate it
via its interface, we will monitor the events it generates by registering a connection point to
receive events
generated by the INmManagerNotify interface. The INmManagerNotify interface is shown below:
interface INmManagerNotify : IUnknown
{
typedef [unique] INmManagerNotify *LPNMMANAGERNOTIFY;
HRESULT NmUI(
[in] CONFN uNotify);
HRESULT ConferenceCreated(
[in] INmConference *pConference);
HRESULT CallCreated(
[in] INmCall *pCall);
}
We don't want to use MFC or ATL and the resulting program will be a console application using straight C++ and COM
client APIs. Our problem is that our application simply uses COM, it doesn't expose any COM functionality itself and
we don't want to add the complexity of being able to serve real COM objects simply so that we can consume events
from a Connection Point. What we'd like to do is simply implement the methods of the notification interface and ask
the Conference Manager object to call us when notifications occur without worrying about the necessary plumbing.
A reusable, self contained, notification class
The work involved to hook up a connection point is fairly straight forward and MFC and ATL could both provide some
help if we were using them, but since we're not we have to do the work ourselves. Given the IUnknown of a connectable
object we need to obtain a pointer to its IConnectionPointContainer interface and from that obtain a pointer to
the required connection point interface. Once we have that, we simply call the Advise() member function, pass
our sink interface and keep hold of the cookie that is returned as we need it to disconnect. If we wrap this work
in a class we can make sure that we disconnect in the destructor. The class might look something like this:
class CNotify
{
public :
CNotify();
~CNotify();
void Connect(
IUnknown *pConnectTo,
REFIID riid,
IUnknown *pIUnknown);
void Disconnect();
bool Connected() const;
private :
DWORD m_dwCookie;
IUnknown *m_pIUnknown;
IConnectionPoint *m_pIConnectionPoint;
IConnectionPointContainer *m_pIConnectionPointContainer;
};
Connecting can be as simple as calling Connect() and passing the connectable object, the IID of the connection point
that we wish to connect to and our sink interface. Disconnecting is equally easy.
The class above is useful but it doesn't solve our problem. We still need to have a COM object to pass as our
sink interface and we don't have one, or want to build one. However, it's relative easy to put together a template
class that's derived from our CNotify class and fills in the missing functionality. The template looks something
like this:
template <class NotifyInterface, const GUID *InterfaceIID> class TNotify :
private CNotify,
public NotifyInterface
{
public :
void Connect(IUnknown *pConnectTo)
{
CNotify::Connect(pConnectTo, *InterfaceIID, this);
}
using CNotify::Disconnect;
using CNotify::Connected;
ULONG STDMETHODCALLTYPE AddRef()
{
return 2;
}
ULONG STDMETHODCALLTYPE Release()
{
return 1;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObj)
{
HRESULT hr = S_OK;
if (riid == IID_IUnknown)
{
*ppvObj = (IUnknown *)this;
}
else if (riid == *InterfaceIID)
{
*ppvObj = (NotifyInterface *)this;
}
else
{
hr = E_NOINTERFACE;
*ppvObj = NULL;
}
return hr;
}
};
TNotify is used as a base class for your notification sink object, it derives from the sink interface that you
want to implement and provides the IUnknown methods for you. You simply have to implement the sink interface
methods and then call Connect() and pass the connectable object that you wish to connect to. Your sink object
might look like this:
class MyConferenceManager : private TNotify
{
public :
HRESULT STDMETHODCALLTYPE NmUI(CONFN confn)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE ConferenceCreated(INmConference *pConference)
{
return S_OK;
}
HRESULT STDMETHODCALLTYPE CallCreated(INmCall *pCall)
{
return S_OK;
}
};
You need to be aware of a few things when using your sink object. The first is that the COM object that is
constructed for you by the TNotify base class expects to reside on the stack, or, at least, it doesn't expect
COM to manage its lifetime (notice the implementation of AddRef() and Release()). Secondly the sink object maintains
references to the connectable object's interfaces, so that it can disconnect when you call Disconnect() or, if you
don't, when it gets destroyed. Finally, since the sink object could make COM calls when it is destroyed you need
to make sure that COM is still initialised at that point - so no calling CoUninitialize() before you destroy your
sink object.
The example code
The example code is slightly more complex than that shown above. Because of all the type mapping that you tend
to have to do to call COM methods from straight C++ we tend to wrap the COM objects in thin C++ wrappers. We do this
with the interface that we obtain for the INmManager object and we use the same wrapper to sink the events. This
leaves us with a single C++ object that exposes both the INmManager interface which translates the argument types to
C++ friendly types and throws exceptions on method failures, and also exposes the INmManagerNotify interface to
notify us of events that occur on the object. As you'll see, creating and using the NetMeeting Conference Manager
COM object and wiring it up to receieve notification events is as simple this:
class MyConferenceManager : public CNmConferenceManager
{
public :
};
...
if (MyConferenceManager::IsNmInstalled())
{
MyConferenceManager confManager;
ULONG options = NM_INIT_NORMAL;
ULONG capabilities = NMCH_ALL;
confManager.Initialize(options, capabilities);
...
Since the CNmConferenceManager object implements all of the notification interface and just does nothing, i.e.
it returns S_OK to all methods, our derived class can choose to simply override the notifications that it wants to
handle. Creating our object initialises COM, so that COM remains initialised until our object is no more, creates
the NetMeeting Conference Manager COM object and connects up the notification sink. We can then simply call methods
on the object and recieve notifications back.
Revision history
- 30th May 2002 - Initial revision.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 24 of 24 (Total in Forum: 24) (Refresh) | FirstPrevNext |
|
 |
|
|
The example for a class that derives TNotify doesn't include the template arguments. Thanks for the code though, very useful.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 please, can you help me. I want to know How to connect to connectble object (COM Object) that fires events through a "dispinterface". this the case of all com objects created with "atl"?? many thanks for any suggestion.
adel.B
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I can reply myself now. Yes it does work for an IDispatch inherited interface. I have to override the Invoke member for every sink I create though. My default Invoke uses ITypeInfo->Invoke, but it fails with DISP_E_MEMBERNOTFOUND. A bit inconvenient to implement the Invoke myself for each sink, but it works anyway.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Sorry about the delay in replying, I've been away.
Glad you solved the problem. I seem to remember that the last time I needed an IDispatch implementation using Invoke I ripped the MFC version out and trimmed the MFC bits off of it and then used that, can't find the code though, it was a long time ago...
Len Holgate www.jetbyte.com The right code, right now.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I used your stuff with another article in here about Dispatch interfaces.
This is my resulting Dispatch implementation. For the actual sink you need to inherit from this template class and implement your interface methods as usual. You also have to override the Invoke method, not sure why, but it doesn't work until you implement Invoke yourself in the derived class. The interface should of course inherit from IDispatch. This may not compile since I wrote this stuff testing with another interface and customized base classes, but more or less this works with a dispinterface.
template class TDispatchSink : public TNotify { public: TDispatchSink(const IID * LibID, int verMajor, int verMinor) : m_pITypeInfo(NULL) { ITypeLib * ptlib; // Type library
// Loading the type library from the Windows registry: if(LoadRegTypeLib(LibId, verMajor, verMinor, 0, &ptlib) == S_OK) { // Get type information for interface of the object. ptlib->GetTypeInfoOfGuid(*InterfaceIID, &m_pITypeInfo); // Release the type library ptlib->Release(); } } ~TDispatchSink() { if (m_pITypeInfo) { m_pITypeInfo->Release(); } } //Not implemented STDMETHOD( GetTypeInfoCount(UINT *pctinfo)) { return E_NOTIMPL; }
//Not really needed but so easy to implement STDMETHOD( GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)) { if (!ppTInfo) { return E_INVALIDARG; } if (m_pITypeInfo) { *ppTInfo = m_pITypeInfo; (*ppTInfo)->AddRef(); } else { return E_FAIL; } return S_OK; } //Not implemented STDMETHOD( GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)) { return E_NOTIMPL; } //Override this method in your derived sink class to avoid DISP_MEMBERNOTFOUND STDMETHOD( Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)) { HRESULT r = E_NOTIMPL; if (m_pITypeInfo) { //This returns DISP_MEMBERNOTFOUND, don't know why. r = m_pITypeInfo->Invoke((IDispatch *)this, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); } return r; } protected: ITypeInfo *m_pITypeInfo; };
Here is an implementation of Invoke as well, but that will of course only work with the interface I am listening to. Just to show anyone interested how to deal with the DISPPARAMS I post it anyway. Most important thing is the order of the params in the array, and of course the typechecking.
STDMETHODIMP CCTIEventSink::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { HRESULT hr = S_OK; UINT dummy, *pErrCode = puArgErr ? puArgErr : &dummy; // protection from NULL pointer passed for error handling
// check for NULL if (pdispparams) { switch (dispidMember) { // call SoftDialError case 1: { if (pdispparams->cArgs == 3) { // Check the argument types in param list int intErrorCode; BSTR sDescription; BSTR sMessage; if (pdispparams->rgvarg[2].vt == VT_I4) { intErrorCode = pdispparams->rgvarg[2].lVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 2; } if (pdispparams->rgvarg[1].vt == VT_BSTR) { sDescription = pdispparams->rgvarg[1].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 1; } if (pdispparams->rgvarg[0].vt == VT_BSTR) { sMessage = pdispparams->rgvarg[0].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 0; } if (hr == S_OK) { SoftDialError(intErrorCode, sDescription, sMessage); } } else hr = DISP_E_BADPARAMCOUNT;
break; }
// call Dial case 2: { if (pdispparams->cArgs == 4) { BSTR sCampaign, sNumber, sSessionID; int intRingTime; if (pdispparams->rgvarg[3].vt == VT_BSTR) { sCampaign = pdispparams->rgvarg[3].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 3; } if (pdispparams->rgvarg[2].vt == VT_BSTR) { sNumber = pdispparams->rgvarg[2].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 2; } if (pdispparams->rgvarg[1].vt == VT_BSTR) { sSessionID = pdispparams->rgvarg[1].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 1; } if (pdispparams->rgvarg[0].vt == VT_I4) { intRingTime = pdispparams->rgvarg[0].lVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 0; } if (hr == S_OK) { Dial(sCampaign, sNumber, sSessionID, intRingTime); } } else hr = DISP_E_BADPARAMCOUNT;
break; }
// call Transfer case 3: { if (pdispparams->cArgs == 6) { BSTR sCampaign, sAgent, sExtension, sTrunk, sSessionID, sSpecific; if (pdispparams->rgvarg[5].vt == VT_BSTR) { sCampaign = pdispparams->rgvarg[5].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 5; } if (pdispparams->rgvarg[4].vt == VT_BSTR) { sAgent = pdispparams->rgvarg[4].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 4; } if (pdispparams->rgvarg[3].vt == VT_BSTR) { sExtension = pdispparams->rgvarg[3].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 3; } if (pdispparams->rgvarg[2].vt == VT_BSTR) { sTrunk = pdispparams->rgvarg[2].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 2; } if (pdispparams->rgvarg[1].vt == VT_BSTR) { sSessionID = pdispparams->rgvarg[1].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 1; } if (pdispparams->rgvarg[0].vt == VT_BSTR) { sSpecific = pdispparams->rgvarg[0].bstrVal; } else { hr = DISP_E_TYPEMISMATCH; *pErrCode = 0; } if (hr == S_OK) { Transfer(sCampaign, sAgent, sExtension, sTrunk, sSessionID, sSpecific); } } else hr = DISP_E_BADPARAMCOUNT;
break; }
default: { hr = DISP_E_MEMBERNOTFOUND; break; } } } else hr = DISP_E_PARAMNOTFOUND;
return hr; }
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
I maybe wrong and correct me if I am, but I previously thought that COM Vtable layout for IUnknown is like that: STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) STDMETHOD_(ULONG,AddRef)(THIS) STDMETHOD_(ULONG,Release)(THIS) As you see QueryInterface is first... Am I missing something?...
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..." Me
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Assuming your point is that I have them in the 'wrong' order then; it doesn't matter what order you define virtual functions in a derived class. The vtable order is defined by the order in which they appear in the base class that originally declares them as virtual.
Len Holgate www.jetbyte.com The right code, right now.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
// You are right. I just can't find that "base" class. Could you point me out?
Forget it: I've figured that out. I was trying to find your base class just looking at source. Now, I've build the project and see that yours CNmConferenceManager is derived from IUnknown. Sorry for confusion.
Regards
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..." Me
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I tried to pass an safearray as a parameter as an event parameter,Fire of the event fail, why and how? thank!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am not sure why it fails. It is probably because a SAFEARRY is not OLE supported type for IDispatch. The client's Invoke implementation might try to free the param!?
To solve it simply use a VARIANT to transport your SAFEARRAY to the client. This should be safe and is complient to OLE too.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Len, I am looking for a sample application in C++ that acts as an out-of-proc server and able to fire events to the VB clients. If you have any such application then please let me know.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Dear sir,
I am experienced in SDK style Windows programing and MFC. Now i want to learn something else and for this purpose i have two choices COM or .NET. I visited your web site's books section and so i obtained the book "Inside COM". But as you mentioned many times that the COM learning period is too much. I don't know any thing about .NET but i expect it as equal or more complex than COM.
So i want your advice for people like me who never done much work on COM, to either start learning COM or move towards .NET
If you recommend me .NET then please tell me the names of books i follow. Actually my experience of learning MFC in the beginning was bad. I started learning MFC from books that directly teaches Doc/View architectures. But thanks that someone mentioned me the book by Jeff Prosise and so i learnt MFC properly. I also came to know that your experience of learning COM in beginning was bad because you did't got the book 'Inside COM' in the begining. So thats why i am asking you to suggest be the right way to learn .NET from proper books.
Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'd say that .Net is probably the way to go and if it doesn't take off then c# is near enough to java for the skills to still be useful... COM is probably a bit too complex and potentially short lived to start getting into now unless you really need to.
I dont have a good .Net book list yet. I'm still looking for the books that make a real difference. Most of them are just shelf fillers...
Len Holgate www.jetbyte.com The right code, right now.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Usually, events interface is a dispinterface. Consider you created a project using ATL and implemented connection points interface. ATL builded a CProxy_IBlaBla helper class you to fire events by it. This is done by invoking through IDispatch::Invoke(). So, how it deals with dispinterface ?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
MaximE wrote: Usually, events interface is a dispinterface.
I disagree.
MaximE wrote: So, how it deals with dispinterface ?
It doesnt.
So I'm in C++ land... 
Len Holgate www.jetbyte.com The right code, right now.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I have an activeX control that developers are starting to implement in console apps and java etc. I needed to override IsInvokeAllowed(...) So they could call methods without the ActiveX being embedded in a container. I also (arghh.) had to provide a c/c++ wrapper class with a header that would prototype the methods appropriately. Here is the issue: The methods were no problem. I used #import and smart pointers. I noticed the #import not only gives me a spart pointer to the object and it's methods but another smart pointer to the events. Do I need to go throught the Connection Point Stuff and 'Advise()' the ActiveX that it has functions to call back or can I use some other magic? I am looking for the most simple way to wrap this. Thanks in advance, Bob
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Bo Carleon wrote: I noticed the #import not only gives me a spart pointer to the object and it's methods but another smart pointer to the events. Do I need to go throught the Connection Point Stuff and 'Advise()' the ActiveX that it has functions to call back or can I use some other magic? I am looking for the most simple way to wrap this.
I don't know. I've not used the #import stuff with connection points. Sorry.
Len Holgate www.jetbyte.com The right code, right now.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Len, this looks truly useful, but you were right, the code sample is a harsh shock to the system 
I wonder if you could (privately or publicly) give a quick run down of how to implement.
e.g.
1. Create Dialog App Project - accept defaults
2. Add ???.*> to the project
3. etc
Regards,
Jason Hattingh
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|