|
When you work on an ATL ActiveX Control, if you were to add a new property to the property map, e.g. :
BEGIN_PROP_MAP(CFPCaptureControl)
...
...
...
PROP_ENTRY_EX("Plugin", 8, CLSID_NULL, IID_IFingerprintCaptureDevice)
END_PROP_MAP()
You may have to delete away the control from the window of a client app's development environment (e.g. VB) and then re-inserting the control.
This is because previously saved stream data for the project (this is usually in the form of a blob) for the control did not have any information on the new property.
After re-insertion and subsequent saving, the full property set of the control (including the new property) will be saved into the stream for the project.
|
|
|
|
|
If you use CComVariant, be sure to refer to the MS SDK oleauto.h header file.
This is because the CComVariant class uses the VarCmp() function which takes 3 parameters. This 3 param version VarCmp() has been superceded by a 4 parameter version.
In the MS SDK oleauto.h header file, the following is defined :
// dwFlags passed to CompareString if a string compare
STDAPI VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid, ULONG dwFlags);
#ifdef __cplusplus
extern "C++" {
// Add wrapper for old ATL headers to call
__inline
HRESULT
STDAPICALLTYPE
VarCmp(LPVARIANT pvarLeft, LPVARIANT pvarRight, LCID lcid) {
return VarCmp(pvarLeft, pvarRight, lcid, 0);
}
} // extern "C++"
#endif
|
|
|
|
|
If you intend to transfer in-memory data (structs, arrays, etc) via a method call (using VARIANT), e.g. :
STDMETHOD(get_Buffer)(VARIANT* pVal);
STDMETHOD(put_Buffer)(VARIANT pVal);
then always do so with a SAFEARRAY instead of directly using memory pointers (via VT_BYREF).
Using VT_BYREF
---------------
If you use VT_BYREF, you would transfer memory data in get_Buffer() this way :
V_VT(pVal) = VT_BYREF;
VT_BYREF(pVal) = &some_struct;
In put_Buffer(), you would do it this way :
if (V_VT(&pVal) == VT_BYREF)
{
memcpy (&some_struct, V_BYREF(&pVal), sizeof(some_struct));
}
The above techniques works most of the time provided :
1. The object which exposes get_Buffer() and put_Buffer() is housed in an in-proc server (i.e. a COM DLL), AND ...
2. The get_Buffer() and put_Buffer() calls are made from the same Apartment. Once cross-apartment calls are made (via proxies), you will get an error code 0x80020008 (DISP_E_BADVARTYPE).
Using SAFEARRAY
----------------
If you use SAFEARRAYs to transfer memory objects, you would do it this way in get_Buffer() :
SAFEARRAY* pSAFEARRAY = NULL;
VariantInit(pVal);
VariantClear(pVal);
CreateSafeArrayFromBytes
(
(LPBYTE)&some_struct,
(ULONG)sizeof(some_struct),
(SAFEARRAY**)&pSAFEARRAY
);
V_VT(pVal) = VT_UI1 | VT_ARRAY;
V_ARRAY(pVal) = pSAFEARRAY;
The way to code put_Buffer() would be :
if (V_VT(pVal) == (VT_UI1 | VT_ARRAY))
{
LPBYTE lpbyByteArray = NULL;
ULONG ulSize = 0;
GetByteArrayFromSafeArray
(
V_ARRAY(&pVal),
&lpbyByteArray,
&ulSize
);
if (lpbyByteArray)
{
memcpy (&some_struct, lpbyByteArray, sizeof(some_struct));
free (lpbyByteArray);
lpbyByteArray = NULL;
}
SafeArrayDestroy(V_ARRAY(&pVal));
}
The above techniques is guarenteed to work all the time whether the calls are made in the same apartment or across apartments using proxies/stubs.
However, there will be performance and overhead penalties.
|
|
|
|
|
Member data of a class which is of reference type must be instantiated during the construction phase of the class, e.g. :
class CMyData
{
int m_i;
};
class CMyDataFactory
{
public :
static CMyData& Create()
{
static CMyData myData;
return myData;
}
};
class CMyClass
{
public :
CMyClass() :
m_MyData(CMyDataFactory::Create())
{
}
~CMyClass()
{
}
CMyData& m_MyData;
};
1. CMyClass contains a member data "m_MyData" which is a reference to a CMyData class.
2. CMyClass::CMyData must be instantiated in the constructor of CMyClass :
CMyClass() :
m_MyData(CMyDataFactory::Create())
{
}
3. Note that m_MyData can be instantiated by a static function call :
CMyDataFactory::Create()
|
|
|
|
|
The registry location of Windows Services and Device Drivers is :
HKLM\System\CurrentControlSet\Services\<driver or service name>
|
|
|
|
|
The time now in Singapore is 12:23 PM Jan 1st 2005. Wonder if it's new year now @ CodeProject ?
|
|
|
|
|
Lim Bio Liong wrote: Wonder if it's new year now @ CodeProject ?
Just Ping you to say Happy New Year 2006 Sir!
"Opinions are neither right nor wrong. I cannot change your opinion. I can, however, change what influences your opinion." - David Crow
cheers,
Alok Gupta
VC Forum Q&A :- I/ IV
|
|
|
|
|
Original Question from "bluetrutle" :
Hi,
In encounter the following problem when using COM objects in a multi -
threaded environment.
I have 2 objects ("A" and "B") that behave differently.
the scenario:
thread 1 - CoInitialize(NULL)
thread 1 is remained alive all the time.
thread 2 - create object
thread 3 - use object ( thru IDispatch )
thread 4 - release object
each thread N (where N > 1), exits after executing the task.
the object is not used concurrently by threads.
there is always only one thread trying to use it.
for object "A" this will work, but for object "B", it wont work.
when I will try to use it, I will get error : RPC_E_SERVER_DIED
in order it to work for object "B", I need to:
thread 2 - create object in thread 1 and marshal it
thread 3,4.. unmarshall before use etc.
so using marshalling cause "B" to work.
But, I dont understand why objects "A" and "B", behave differently.
I wrote object "A" by myself using ATL ( CComSingleThreadModel ) and it
is marked in registry as "ThreadingModel=Apartment"
I dont have the code \ documentation for object "B", but it registers
itself also as "ThreadingModel=Apartment",
and I know it was written in VB.
I thought I need to marshal anyway, but when I use object "A" without
marshalling it works.
I try to understand \ explain this behavior.
Any help is appreciated.
Thanks,
Dave.
|
|
|
|
|
An answer from Alexander Nickolov
This is what's called random behavior. Did you
expect it to always work when you break the rules?
|
|
|
|
|
Reply from "blueturtle"
Hi,
I expected, that without marshalling, it will _never_ work.
So how come it works (on some objects) ?
I try to understand it.
What is the piece of code in the COM server that cause the "random"
behavior ?
Or maybe I'm doing something wrong somewhere.
( Forgot to mention - each thread N has CoInitialize() )
Thanks,
Dave.
|
|
|
|
|
Answer from Bio :
Hello blueturtle,
Here are my thoughts concerning why "A" worked while "B" did not :
1. The fact that "A" and "B" are both marked in the registry as
"ThreadingModel=Apartment" indicates that they are both STA (Single-Threaded
Apartment) objects.
2. Now, for both "A" and "B", the whole idea behind making them STA objects
is to ensure their thread safety without having to use additional
thread-synchronization code. However, as you have explained, you have already
personally ensured that no two threads access them at any time.
3. Therefore, as far as "A" is concerned, passing a direct pointer to it to
other threads and then calling its methods from those threads, while
technically unsound, did not pose any danger. You -have- broken the rules.
But the rules were not needed anyway.
4. If you had marshalled "A" across to thread 3 and thread 4, thread 3 and
thread 4 will not receive a direct pointer to "A". They will instead each
receive a proxy to "A". Method calls to "A" will have to go through the
marshalling process and a private message will be sent to the message loop of
thread 2 (the thread which created "A") which will eventually result in the
execution of the method.
5. Point 4 entails some performance penalties as you can imagine. In fact,
since you have ensured that no two threads will ever touch "A" at anytime,
"A" is technically a Free-Threaded Object.
6. Now, on to "B". You mentioned that "B" is written in VB. This can be
significant. Because STA objects have the advantage of thread-affinity, VB
uses thread-local storage (TLS) to store various state information on
VB-generated COM objects. The thread whose local storage is used is the
thread of the STA in which the COM object lives in and is also the one which
created it.
7. Using these VB-generated COM objects from an external thread without
marshalling is thus fatal. They will not be able to access information stored
inside their TLS.
Hope the above helps.
Best Regards,
Bio.
PS : just curious : Did you transport the interface pointer from thread to
thread via thread parameters ?
|
|
|
|
|
Reply from "blueturtle" :
Hi,
Thanks for your help.
The information about VB's implementation using the TLS was important
for me.
I had to verify in which cases I can avoid the marshalling (none left
in my case...).
Regarding your question:
The information is not actually passed between the threads.
In the case where I had to use the marshalling, it is a kind of an
application server
which accept requests from web browsers, and each request is executed
in a new thread ( to minimize amount of running threads )
So when a session starts, the web browser can ask to load object X,
in return I send back an identifier that represents the marshalled
object.
In the next request, a new thread will start to execute it, unmarshal,
IDispatch::Invoke, marshal, etc.
What I like about it, is that it should work with any COM object (
there is no set of predefined of objects ),
so it is very interesting to make it work with all the scenarios,
parameter types, etc.
What I dont like about it, is that it should work with _every_ COM
object...
So I have to solve many internal problems of customers,
E.g: TLS (yes again, but nothing to do with marshalling this time )
I encountered a COM object that was using TlsAlloc (inside ACE
library),
and that collided (a nice random crash) with some other code which used
TlsAlloc in that thread,
because the ACE library was compiled with the wrong value for maximum
indices for that OS.
( from MSDN:
Windows 2000/XP: There is a limit of 1088 TLS indexes per process.
Windows 98/Me: There is a limit of 80 TLS indexes per process. )
Thanks again for your help,
Dave.
|
|
|
|
|
It's now 2:15 pm 31st Dec 2004 in Singapore.
Happy New Year to all CodeProject Members !
- Bio.
|
|
|
|
|
A coclass is COM's way of defining a class (class in the object-oriented sense) which is meant to be language independent.
An IDL file is what COM provides that allows developers to define such language independent object-oriented classes.
An IDL file is compiled by the MIDL compiler into a Type Library (.TLB file) which is a binary form of an IDL file meant to be processed by various language compilers (e.g. VB, VC++, Delphi, etc).
The end result of such .TLB processing is that the specific language compiler produces the language-specific constructs (VB classes for VB, C++ classes, various structs, macros and typedefs for VC++) that represent the coclass defined in the .TLB (and ultimately that which was defined in the originating .IDL).
When you look at an example coclass definition in an IDL :
coclass MyObject
{
[default] interface IMyObject;
[default, source] dispinterface _IMyObjectEvents;
};
it is declaring a COM class named MyObject which must implement an interface named IMyObject and which supports (not implement) the event interface _IMyObjectEvents.
This is conceptually equivalent to defining a C++ class like this :
class CSomeObject : public ISomeInterface
{
...
...
...
};
where ISomeInterface is a C++ virtual class.
It is up to the individual language compiler to produce whatever code (in the specific compiler's language) is necessary for its user to implement and ultimately produce a binary which can be deemed by COM to be of coclass MyObject.
Now, in languages like C++, we can use CoCreateInstance() in which we can specify the CLSID of the coclass and select the interface from that coclass that we want to use to interact with that coclass. Calling CoCreateInstance() like this :
STDAPI CoCreateInstance(
CLSID_MyObject,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMyObject,
(void**)&m_pIMyObject
);
is conceptually equivalent to :
ISomeInterface* pISomeInterface = NULL;
pISomeInterface = new CSomeObject();
In the first case, we are saying to the COM sub-system that we want to obtain a pointer to an object that implements the IMyObject interface and we want coclass CLSID_MyObject's particular implementation of this interface.
In the second case, we are saying that we want to create an instance of a C++ class that implements the interface ISomeInterface and we are using CSomeObject as that C++ class.
Do you see the equivalence ? A coclass, then, is an object-oriented class in COM's own language.
|
|
|
|
|
Note that if a thread (in a COM Exe Server) is initialized with ::CoInitializeEx(NULL, COINIT_MULTITHREADED) and this thread is used to register a class object :
DWORD WINAPI ThreadFunc(LPVOID lpvParameter)
{
LPDWORD lpdwCookieReceiver = (LPDWORD)lpvParameter;
::CoInitializeEx(NULL, COINIT_MULTITHREADED);
RegisterClassObject_CTestServer02(lpdwCookieReceiver);
::CoUninitialize();
return 0;
}
void RegisterClassObject_CTestServer02(LPDWORD lpdwCookieReceiver)
{
HRESULT hr = S_OK;
CTestServer02_Factory* pCTestServer02_Factory = new CTestServer02_Factory();
if (pCTestServer02_Factory)
{
IUnknown* pIUnknown = NULL;
pCTestServer02_Factory -> QueryInterface(IID_IUnknown, (void**)&pIUnknown);
hr = ::CoRegisterClassObject
(
CLSID_TestServer02,
pIUnknown,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
lpdwCookieReceiver
);
pIUnknown -> Release();
pIUnknown = NULL;
pCTestServer02_Factory -> Release();
pCTestServer02_Factory = NULL;
}
}
This thread need not stay alive for the registered class object. In fact, as you can see, the thread simply exits after performing the registration.
The pointer to the class factory remains alive inside the COM engine somewhere.
However, this will not work if the thread is initialized with ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).
|
|
|
|
|
1. After some time of researching, I found that a COM Object in a COM EXE Server cannot decide on its own Apartment Model (Apartment, Multithreaded, etc).
2. This is so despite indicating the model you want when creating an ATL COM object inside an EXE Server using the ATL Wizard.
3. The way to indicate the Apartment Model for your COM Object is to place your call to CoRegisterClassObject() on your COM Object Class Factory in a thread that has been initialized with CoInitializeEx(NULL, COINIT_MULTITHREADED) or CoInitializeEx(NULL, COINIT_APARTMENTTHREADED).
|
|
|
|
|
In his book, "Essential COM", Don Box wrote :
From a programming perspective, apartment membership is an interface pointer attribute, not an object attribute. When an interface pointer is returned from a COM API call or from a method invokation, the thread that invoked the API call or method determines which apartment the resultant interface pointer belongs to.
If the call returns a pointer to the actual object, then the object itself resides in the calling thread's apartment. Often the object cannot reside in the caller's apartment, ether because the object already exists in a different process or host machine or because the concurrency requirements of the object are incompatible with the client's apartment. In these cases, the client receives a pointer to a proxy.
|
|
|
|
|
In his book, "Essential COM", Don Box wrote :
In COM, a proxy is an object that is semantically identical to an object in another apartment.
|
|
|
|
|
The DispatchMessage function dispatches a message to a window procedure. It is typically used to dispatch a message retrieved by the GetMessage function.
|
|
|
|
|
The PostThreadMessage function posts a message to the message queue of the specified thread.
The thread to which the message is posted must have created a message queue, or else the call to PostThreadMessage fails. Use one of the following methods to handle this situation:
* Call PostThreadMessage. If it fails, call the Sleep function and call PostThreadMessage again. Repeat until PostThreadMessage succeeds.
* Create an event object, then create the thread. Use the WaitForSingleObject function to wait for the event to be set to the signaled state before calling PostThreadMessage. In the thread to which the message will be posted, call PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE) to force the system to create the message queue. Set the event, to indicate that the thread is ready to receive posted messages.
The thread to which the message is posted retrieves the message by calling the GetMessage or PeekMessage function. The hwnd member of the returned MSG structure is NULL.
Messages sent by PostThreadMessage are not associated with a window. Messages that are not associated with a window cannot be dispatched by the DispatchMessage function. Therefore, if the recipient thread is in a modal loop (as used by MessageBox or DialogBox), the messages will be lost. To intercept thread messages while in a modal loop, use a thread-specific hook.
|
|
|
|
|
In COM, event handling is achieved via Connection Points. The COM object which fires events is a Connection Point Container. It recognizes several Connection Points.
Hence,
COM Object That Fires Events
- implements IConnectionPointContainer.
- contains one or more IConnectionPoint implementations.
- calls event interface methods implemented by event clients.
COM Client that listens to events
- implements Event Interface methods.
- searches for a suitable connection point in target COM object (that fires events) via IConnectionPointContainer.FindConnectionPoint().
- connects with event firing object via IConnectionPoint.Advise().
- disconnects with event firing obejct via IConnectionPoint.Unadvise().
|
|
|
|
|
I try your TEventHandler template in the activex component that I design. I hope to use this to receive the events of flash activex I create in my activex component.
But I came across some compile errors:
--------------------Configuration: SmartFlash - Win32 Debug--------------------
Compiling...
StdAfx.cpp
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(25) : error C2872: 'DISPPARAMS' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(27) : error C2872: 'EXCEPINFO' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(110) : error C2872: 'DISPPARAMS' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(111) : error C2872: 'EXCEPINFO' : ambiguous symbol
C:\Projects\SmartFlash (1cm)\SmartFlash\TEventHandler.h(170) : see reference to class template instantiation 'TEventHandlerNamespace::TEventHandler<event_handler_class,device_interface,device_event_interface>' being compiled
Error executing cl.exe.
SmartFlash.ocx - 4 error(s), 0 warning(s)
It's about the following snippet
typedef HRESULT (event_handler_class::*parent_on_invoke)
(
TEventHandler<event_handler_class, device_interface,="" device_event_interface="">* pthis,
DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pdispparams,
VARIANT* pvarResult,
EXCEPINFO* pexcepinfo,
UINT* puArgErr
);
I don't have any hints to solve this problem, can you give me some suggestion?
|
|
|
|
|
For those of you who have tried to develop ASP.NET Web Applications using Visual Studio .NET under the Windows 2000 platform and faced a message indicating that ASP.NET is not installed on your Web Server (i.e. IIS), you need to perform the following :
1. Install ASP.NET on your system.
1.1. To do this, start up the Visual Studio .NET 2003 Command Prompt and type the following :
aspnet_regiis /i
1.2 You will see the following line output on your console window :
Start installing ASP.NET (1.1.4322.0).
After a pause of some time, you will see the following line output :
Finished installing ASP.NET (1.1.4322.0).
2. Of course, in order to be able to install ASP.NET, you must first have Microsoft .NET Framework installed on your target web server system.
2.1 If you are unsure whether ASP.NET has actually been installed on your system even after installing the .NET framework, go to the ASP.NET home page to download the Microsoft .NET Framework Version 1.1 Redistribution Package :
http://asp.net/Default.aspx?tabindex=0&tabid=1
|
|
|
|
|
Just like to share with all a note about one instance where the Type Library GUID and its version number can play a very significant role.
1. Background
----------------------
1.1 I was developing a wrapper ATL COM Server for a COM Server written in Visual Basic.
1.2 The original COM Server written in Visual Basic has a set of events that it fires on certain occassions.
1.3 What my wrapper COM Server needed to do was to receive these events from the VB COM Server and then perform some work before firing the same event up to my client.
1.4 Firing events to my client was the simple part. The part that presented some bumps along the road was that of receiving events from my VB COM Server.
2. Some Info on implementing Event Listening
--------------------------------------------------------------------
2.1To receive events from an external COM Server inside your own ATL COM Server, you need to derive your CComObjectRootEx object from the IDispEventImpl class : e.g. :
class ATL_NO_VTABLE CIrisACUWrapper :
public CComObjectRootEx<CComSingleThreadModel>,
...
...
...
public IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0> // <--- publicly derive from IDispEventImpl.
2.2 You also need to declare an Event Sink Map in your ATL COM Server class : e.g. :
BEGIN_SINK_MAP(CIrisACUWrapper)
SINK_ENTRY_EX(1, __uuidof(__ClassIrisACU), 1, OnCaptureComplete)
END_SINK_MAP()
2.3 For more info on Event Sinks, refer to MSDN documentation for topics :
IDispEventImpl
and
Supporting IDispEventImpl
2.4 You also need to connect to the COM Server that fires the events you are interested in receiving. This is related to Connection Points. A sample implementation is presented below :
long SetupWrappedObjectEventConnections()
{
IUnknown* pIUnknown_WrappedObject = NULL;
long lRet = 0;
m_spICaptureDevice -> QueryInterface (IID_IUnknown, (void**)&pIUnknown_WrappedObject);
if (pIUnknown_WrappedObject)
{
IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0>::DispEventAdvise(pIUnknown_WrappedObject);
pIUnknown_WrappedObject -> Release();
pIUnknown_WrappedObject = NULL;
}
return lRet;
}
2.5 At the end of your application, you will also want to tell your COM Server to stop sending you events. A sample implementation is presented below :
long ShutdownWrappedObjectEventConnections()
{
IUnknown* pIUnknown_WrappedObject = NULL;
long lRet = 0;
m_spICaptureDevice -> QueryInterface (IID_IUnknown, (void**)&pIUnknown_WrappedObject);
if (pIUnknown_WrappedObject)
{
IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0>::DispEventUnadvise(pIUnknown_WrappedObject);
pIUnknown_WrappedObject -> Release();
pIUnknown_WrappedObject = NULL;
}
return lRet;
}
|
|
|
|
|
3. The Importance of the (event firing) COM Server's Type Lib GUID and its Version Number
-----------------------------------------------------------------------------------------------------------------------------------------
3.1 Note the way that you should declare the derivation of your CComObjectRootEx object from the IDispEventImpl class :
class ATL_NO_VTABLE CIrisACUWrapper :
public CComObjectRootEx<ccomsinglethreadmodel>,
...
...
...
public IDispEventImpl<1, CIrisACUWrapper, &__uuidof(__ClassIrisACU), &LIBID_IrisACULib, 4, 0> // <--- publicly derive from IDispEventImpl.
3.2 The 4th parameter, the Type Library GUID, is the GUID of the Type Library of the COM Server that fires the set of events (for which we, as its client, wants to receive).
3.3 The 5th and 6th parameters, i.e. the last 2 numbers, i.e. 4 and 0, are very important too.
3.4 They form the version number of the Type Library whose GUID is specified.
3.5 You can find out the Type Library GUID and the version number from the "library" statement in the IDL file of the COM Server :
e.g. :
//
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: IrisACU.dll
[
uuid(EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80), // <-- This is the GUID of the type library.
version(4.0) // <-- this is the version we want to know.
]
library IrisACU
{
...
}
3.6 Or you can find out this info from the Registry :
Lookup the HKEY_CLASSES_ROOT\TypeLib\ key and search for the sub key that is named by the GUID of the Type Lib : e.g. :
HKEY_CLASSES_ROOT\TypeLib\{EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80}
Immediately after this key is the version number subkey : e.g. :
HKEY_CLASSES_ROOT\TypeLib\{EF2DFFCB-D5CA-4AE0-9A53-1C4E04EC3F80}\4.0
3.7 The purpose of getting the GUID and version numbers of the type library is simply for the ATL Engine to locate the correct Type Library of the COM Server that fires the events.
3.8 The client ATL COM Server engine (i.e. the side that RECEIVES the event) needs to determine the INDEX (v-table index) and the DESCRIPTION (call type, number of parameters, parameter types, etc) of the EVENT function that is to be invoked (i.e. fired) from the (event firing) COM Server.
3.9 In other words, the ATL Engine needs to know the signature of the event function. This is part of ATL's architecture for sending/receiving events.
3.10 The correct Type Library GUID and Version Info must be given in order for the ATL Engine to load the appropriate Type Lib.
3.11 If this fails, the event will still be fired from the (event firing) COM Server but this will not be received by the event-receiving client.
|
|
|
|
|