Re: HOWTO set up minimal sink object in console COM client?

From: Alexander Nickolov (agnickolov_at_mvps.org)
Date: 03/10/05


Date: Thu, 10 Mar 2005 09:52:29 -0800

My post was mainly about regular COM interface sinks.
You can instantiate a class derived from IDispEventImpl
any way you want. And, yes, the sink is a COM object,
usually private, e.g. not publicly creatable (no class factory).
In case of IDispEvent[Simple]Impl the COM object is
entirely contained within the base class - an exampe where
you might have multiple COM objects in a single class (if
you derive multiple times from IDispEventImpl - each
derivation adds a sink - a COM object).

-- 
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnickolov@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
"Mateusz Łoskot" <mateusz@loskot.net> wrote in message 
news:%234BO%23NSJFHA.3596@TK2MSFTNGP14.phx.gbl...
>
>
> Hi,
>
> Thank you Alexander for your help.
> Below I'm trying to explaining what I've done already.
>
> Please, let me to add a little explanation related to my objectives.
> Here is my MyObject declaration:
>
> class ATL_NO_VTABLE CMyObject :
> public CWindowImpl<CMyObject>,
> public CComObjectRootEx<CComSingleThreadModel>,
> public CComCoClass<CMyObject, &CLSID_MyObject>,
> public ISupportErrorInfo,
> public IConnectionPointContainerImpl<CMyObject>,
> public CProxy_IMyObjectEvents<CMyObject>,
> public IDispatchImpl<IMyObject, &IID_IMyObject, &LIBID_MYOBJECTLib,
> /*wMajor =*/ 1, /*wMinor =*/ 0>
> {
> // ....
> STDMETHOD(StartLongTask)(void); // creates worker thread
> };
>
> I create worker thread in StartLongTask call (I use _beginthreadex)
> so I derive my CMyObject from CWindowImpl because I want to be able to
> PostMessage from Thread Procedure to the COM Object in order to fire
> events to client.
> This is a simple architecture based on the Microsoft solution presented
> in this article:
>
> http://support.microsoft.com/kb/q196026/
>
> So, my console based COM object client should be able to response to
> events fired
> by the COM object above, the same way as VB client does.
>
> In article <ezqqRd0IFHA.1996@TK2MSFTNGP12.phx.gbl> Alexander Nickolov
> wrote:
>> You start a regular console project, then add minimal ATL
>> support - atlbase.h and atlcom.h, with the _Module in between
>> in ATL3 (ATL7 doesn't need it anymore).
>
> Yes, I have it declared in stdafx.h of my client project:
>
> #include <atlbase.h>
> extern CComModule _Module; // declared in main.cpp of the client
> #include <atlcom.h>
>
> Then in main.cpp file of my client (there is also _tmain app entry
> point)
> I have _Module and object map declared:
>
> CComModule _Module;
> BEGIN_OBJECT_MAP(ObjectMap) // intencionally left empty
> END_OBJECT_MAP()
>
>
>> Then depending
>> on the type of event interface you are suing you either need
>> IDispEvent[Simpel]Impl for a dispinterface, or a simle ATL
>> object for a COM interface. For the latter you need to derive
>> from CComObjectRootEx<CComXxxThreadModel> and
>> the event interface you are implementing, then add an interface
>> map with that interface, and finally implement the interface
>> itself.
>
> So, does it mean sink object is always COM object?
> I think so but I'd like to be sure :-)
>
> OK, here is my versiom - dispinterface based (MyObject is main COM
> object).
> My MYOBJECT.idl contains source interface declared as follows:
>
> dispinterface _IMyObjectEvents
> {
> // metthods
> [id(1)] HRESULT OnTaskStarted([in] long Counter);
> }
>
> Then my sink class is declared this way:
>
> class CEventHandler :
> public IDispEventImpl<0, CEventHandler, &DIID__IMyObjectEvents,
> &LIBID_MYOBJECTLib, 1, 0>
> {
> void __stdcall OnTaskStarted(long counter)
> { g_bFinish = true; // event handled - set global flag }
> BEGIN_SINK_MAP(CEventHandler)
> SINK_ENTRY_EX(0, DIID__IMyObjectEvents, 1, OnTaskStarted)
> END_SINK_MAP()
> };
>
>> You use AtlAdvise/AtlUnadvise to attach/detach your
>> sink object (for the dispinterface sink you use DispEventAdvise
>> instead).
>
> Yes, I use DispEventAdvise as my sink is derived
> from IDispEventImpl and dispinterface.
>
>> You use CComObject<your_sink>::CreateInstance
>> to create an instance of your sink object.
>> Beware your object
>> has refernce count of zeor when created so you should imediately
>> AddRef and Release after finished advising (to take care of
>> proper disposal in case of failure).
>> Use GetUnknown() to get
>> your sink object's IUnknown pointer (to pass to AtlAdvise).
>>
>
> Hm, in one of examples on m.p.vc.atl group I found object creation
> using "new" operator withouth any refs counting (add/relese) like this:
>
> CEventHandler * pSink = new CEventHandler();
> pSink->DispEventAdvise(spUnk);
>
> But it seems very suspicious to me.
> So, now I know I have to create sink object as common COM object
> in COM way. Right?
>
> OK, thanks for giving me such great light to my COM knowledge :-)
>
> Below I put my _tmain() function of my console-basd COM client.
> I have one more problem with message map
> Could you be so kind and review my code below and explain
> things marked in comments?
>
> // COM _Module declared above
> // COM object map declared above
> //
> int _tmain(int argc, _TCHAR* argv[])
> {
> // globals
> g_hInstance = NULL;
> g_bFinish = FALSE;
>
> std::cout << "INITIALIZE" << std::endl;
>
> // ##### Here I use COINIT_MULTITHREADED, is that correct for my COM
> // object above?
> // I think I could use apartment too, right?
> HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
> _ASSERTE(SUCCEEDED(hr));
>
> { // start of COM object life block
>
> std::cout << " INIT" << std::endl;
> g_hInstance = ::GetModuleHandle(NULL);
> _Module.Init(ObjectMap, g_hInstance, &GUID_NULL);
>
> IMyObjectPtr spMyObj;
> spMyObj.CreateInstance(CLSID_MyObject);
> IUnknownPtr spUnk = spMyObj;
>
> //CEventHandler * pSink = new CEventHandler(); //
> CComObject<CEventHandler> * pSink;
> hr = CComObject<CEventHandler>::CreateInstance(&pSink)
> _ASSERTE(SUCCEEDED(hr));
>
> pSink->AddRef();
> pSink->DispEventAdvise(spUnk);
>
> std::cout << "  TASK" << std::endl;
> spMyObj->StartLongTask(); // creates worker thread
>
> // ##### Here is my problem, when I uncomment
> // all those *Message calls, this loop hangs in
> // GetMessage. I'm really confused if I should use real message
> // loop here, should I? This is console app, so it has no window :-)
> // but COM object has its own message loop implemented by CWindowImpl
> // so I think here should be only non-message-loop waiting for
> // flag set by event handler (waiting for event).
> // Is it good solution?
>
> MSG msg;
> while (!g_bFinish) // && GetMessage(&msg, NULL, 0, 0))
> {
> //TranslateMessage(&msg);
> if (g_bFinish) // event handler sets this flag
> std::cout << "   FINISH" << std::endl;
> //DispatchMessage(&msg);
> }
>
> pSink->DispEventUnadvise(spUnk);
> pSink->Release();
> std::cout << "  END" << std::endl;
>
> _Module.Term();
> std::cout << " TERM" << std::endl;
>
> } // end of COM object life block
>
> ::CoUninitialize();
> std::cout << "UNINITIALIZE" << std::endl;
>
> return 0;
> } // end of _tmain()
>
>
> Uff, that was my another long post on this group>
> Thank you very much for your help.
>
> Greets
>
> --
>
> Mateusz Łoskot
> e-mail: mateusz at loskot dot net
> www:    http://loskot.net/about/contact/
>