|
A potentially useful #import attribute is the inject_statement() attribute.
This attribute can be used to insert various statements inside a generated .TLH file.
For example, I was trying to import the EXCEL type library embededd inside EXCEL.EXE. However, this type library required several declarations from MSO.DLL.
What I did was to import both EXCEL.EXE and MSO.DLL and inserted an "#include" statement in the generated EXCEL.TLH file. This was what I did :
#include <windows.h>
#import "C:\Program Files\Common Files\Microsoft Shared\Office10\MSO.DLL"
#import "C:\Program Files\Microsoft Office\Office10\EXCEL.EXE" inject_statement("#include \"MSO.tlh\"") exclude("IRange", "IDummy") raw_interfaces_only
The "inject_statement" attribute led to the following being generated inside EXCEL.TLH :
namespace Excel {
//
// User-injected statements
//
#include "MSO.tlh"
}
Note the comment "User-injected statements" and the #include "MSO.tlh".
Cool.
- Bio.
|
|
|
|
|
The following case is extracted from the MSDN Visual C++ Forum :
Posted by Anand K Reddy :
I have created an MFC ActiveX dll and I have exposed the methods in Dispatch map. I am able to call the method and get the result using C#.NET client but unable to call from Javascript. When the method is called from Javascript and exception is thrown without any message. I am not able to debug where exactly the problem is.
<code>
The Javascript code is pasted below and the sample activeX control is attached.
<HTML>
<HEAD>
<OBJECT classid="2D0B18FB-673F-4B90-865F-3EC067C9DEA9" id="a"></OBJECT>
<TITLE></TITLE>
<script type='text/javascript' language='javascript'>
function callTest()
{
try{
var myobject;
myobject = new ActiveXObject("SAMPLEACTIVEX.sampleActiveXCtrl.1");
if(myobject != null) alert("Object Found");
if((myobject.MyAdd(2,3))!=null)
alert("got result");
else
alert("Result is Null");
}
catch(e)
{
alert("exception:"+e.message);
}
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE="button" value="TEST" onClick="callTest()">
</BODY>
</HTML>
</code>
|
|
|
|
|
The following is Anand's IDL file snippet :
#include <olectl.h>
#include <idispids.h>
#define DISPID_ADD (1025314)
#define DISPID_MYADD (1025315)
[ uuid(4B16D152-7D25-48A4-8DFE-EABF2AFB7FA3), version(1.0),
helpfile("sampleActiveX.hlp"),
helpstring("sampleActiveX ActiveX Control module"),
control ]
library sampleActiveXLib
{
importlib(STDOLE_TLB);
// Primary dispatch interface for CsampleActiveXCtrl
[ uuid(D99114F8-641E-471E-AA70-5644C0A91E9A),
helpstring("Dispatch interface for sampleActiveX Control")]
dispinterface _DsampleActiveX
{
properties:
methods:
[id(DISPID_ABOUTBOX)] void AboutBox();
[id(DISPID_ADD)] long Add(short x, short y);
[id(DISPID_MYADD)] long MyAdd(short param1, short param2);
};
// Event dispatch interface for CsampleActiveXCtrl
[ uuid(AD8B5691-3F3D-413E-8468-77F7440ADD33),
helpstring("Event interface for sampleActiveX Control") ]
dispinterface _DsampleActiveXEvents
{
properties:
// Event interface has no properties
methods:
};
// Class information for CsampleActiveXCtrl
[ uuid(2D0B18FB-673F-4B90-865F-3EC067C9DEA9),
helpstring("sampleActiveX Control"), control ]
coclass sampleActiveX
{
[default] dispinterface _DsampleActiveX;
[default, source] dispinterface _DsampleActiveXEvents;
};
}
|
|
|
|
|
The following was my reply to Anand :
Hello Anand,
Quick answer :
1. In your OBJECT tag :
<OBJECT classid="2D0B18FB-673F-4B90-865F-3EC067C9DEA9" id="a"></OBJECT>
add a preceding "CLSID:" before the GUID of your ActiveX control :
<OBJECT classid="CLSID:2D0B18FB-673F-4B90-865F-3EC067C9DEA9" id="a"></OBJECT>
2. Do not create your ActiveX control as an ActiveX object :
// var myobject; // remove
// myobject = new ActiveXObject("SAMPLEACTIVEX.sampleActiveXCtrl.1"); // remove
if(a != null) alert("Object Found");
if((a.MyAdd(2,3))!=null)
alert("got result");
else
alert("Result is Null");
Rather, use the ActiveX control (i.e. "a") referred to in your OBJECT tag.
Pls try and let me know if you succeed. I'll return with a longer answer on another post.
- Bio.
|
|
|
|
|
The following is a continuation of Bio's reply :
Hello Anand,
As promised, the following is my long answer :
Long Answer
1. Unless the value of the CLSID attribute of the OBJECT tag in your HTML code contains the symbol "CLSID:" as in :
<OBJECT classid="CLSID:2D0B18FB-673F-4B90-865F-3EC067C9DEA9" id="a"></OBJECT>
The CLASSID attribute remains invalid and so your ActiveX will not get created.
2. Next, comes the problem with "myobject" :
myobject = new ActiveXObject("SAMPLEACTIVEX.sampleActiveXCtrl.1");
The issue has to do with the calling of the IPersistPropertyBag::InitNew() method of your MFC ActiveX Control. This interface method is called by the MSHTML.DLL code within IExplorer.exe.
3. The IPersistPropertyBag::InitNew() method is implemented for you by MFC via the COleControl::XPersistPropertyBag::InitNew() method. Your MFC class would have been derived from COleControl and so this function is already embedded within your MFC class.
3. Inside COleControl::XPersistPropertyBag::InitNew(), you will notice a call to set COleControl::m_bInitialized to TRUE. This is important as we will see later.
4. Now, when any of your control's methods is called, the JScript engine will first get MSHTML to obtain the ID of your method. This is the essence of late binding. Hence, if MyAdd() is to be invoked, JScript will need to know the ID of the MyAdd() function which in your ActiveX control would be DISPID_MYADD.
5. MSHTML will inquire of your ActiveX the ID of MyAdd() by calling your ActiveX control's IDispatch::GetIDsOfNames() method. This is implemented for you by MFC with the function : COleDispatchImpl::GetIDsOfNames().
6. After obtaining this ID, the IDispatch::Invoke() method will be called. The IDispatch::Invoke() method is implemented for your ActiveX by the COleDispatchImpl::Invoke() method. It is here that your MyAdd() function should get called. However, there is a line within COleDispatchImpl::Invoke() that goes like this :
// allow subclass to disable Invoke
if (!pThis->IsInvokeAllowed(dispid))
return E_UNEXPECTED;
This is the function COleControl::IsInvokeAllowed() and it is the "archilles tendon" of your problem.
7. Examining COleControl::IsInvokeAllowed() :
BOOL COleControl::IsInvokeAllowed(DISPID)
{
return m_bInitialized;
}
We will note that the state of the COleControl::m_bInitialized, if FALSE, will result in the premature conclusion of the COleDispatchImpl::Invoke() method, resulting in the return of the E_UNEXPECTED HRESULT. This is the cause of the exception in your Javascript.
8. In summary, adding "CLSID:" to the CLASSID attribute of the OBJECT tag is not sufficient. You must use the ActiveX control as declared by the OBJECT tag whose id is "a". The use of the ActiveXObject() function is meant to create and return a COM interface. There are 2 problems with the use of the ActiveXObject() function in relation to your Javascript :
8.1 By declaring "myobject" and then setting it to a new ActiveXObject(), a brand new instance of your ActiveX control will be created. The "a" object will remain but it will not be used. The calling of IPersists* interface methods of your ActiveX control will not be called.
8.2 "myobject" will end up without its COleControl::XPersistPropertyBag::InitNew() ever being called which will result in its COleControl::m_bInitialized remaining FALSE. This results in the COleDispatchImpl::Invoke() function returning E_UNEXPECTED.
Hence my recommendation that you use the "a" object and remove the declaration and use of "myobject".
I hope the above rather long explanation helps.
- Bio.
|
|
|
|
|
The following is take from a post by Nibu Thomas (MVP) in the Microsoft Visual C++ Language forum :
There is only one way to delete properly and it depends on the way you allocated.
So if you allocate with
int* ptr = new int[100]
delete [] ptr; // then delete like this
then you got to delete with delete [] ptr. Don't get fooled by what you see in the output window of visual studio. Because visual studio places a marker around at the beginning of the memory block to check whether delete has been called or not (it's provided just to help). It does not differentiate between 'delete []' and 'delete'.
So if you allocate with
int * ptr = new int;
delete ptr; // then delete like this
To get rid of these allocation and de-allocation headaches use
std::vector<int> Ints;
|
|
|
|
|
[This thread is a work in progress]
There was recently a question posted on the COM forum here at CodeProject and it involved a temporary VARIANT object being created (in a VB client code) to fill an [in, out, optional] VARIANT* parameter of a method call.
The person who posted the original question was one GuimaSun and his COM server code was listed as follows :
<pre>
STDMETHODIMP CDCSClient::CallService( VARIANT *p1 )
{
...
...
VariantClear( p );
VariantInit( p );
p->vt = VT_BSTR | VT_BYREF;
BSTR *pBSTR = new BSTR;
*pBSTR = SysAllocString( L"abc" );
p->pbstrVal = pBSTR;
...
...
...
}
</pre>
His VB client code was :
<pre>
For i = 1 To 1000
Dim myInt As Integer
myInt = 3
ret = client.CallService(myInt)
Next
</pre>
Apparently, his COM server code was changing the Variant Type of the input VARIANT pointer "p" and assigning it to contain a BSTR.
His VB code experienced memory leaks due to the BSTR (created inside his COM CallService() method) not being ::SysFreeString()'ed.
After much discussion with several members of the forum, Vi2 came up with a code suggestion for GuimaSun as follows :
<pre>
STDMETHODIMP CDCSClient::CallService(/*[in,out]*/ VARIANT *p)
{
if (V_VT(p) == (VT_VARIANT | VT_BYREF))
{
// only here you can change the type of passed variable (see VB example below)
VARIANT *p2 = V_VARIANTREF(p);
p2->vt = VT_BSTR;
p2->bstrVal = SysAllocString( L"abc" );
return S_OK;
}
}
Dim v As Variant
v = 1
Debug.Print TypeName(v) & " " & v
Call obj.CallService(v)
Debug.Print TypeName(v) & " " & v
</pre>
Basically, only a VARIANT of Variant Type (VT_VARIANT | VT_BYREF) can be modified to be of a different VT.
modified on Thursday, August 13, 2009 12:13 PM
|
|
|
|
|
1. There are two ways to reference types defined externally in an IDL file :
1.1 Via the [import] MIDL directive.
1.2 Via the [importlib] MIDL directive.
2. An example use of the [import] directive is listed below :
import "msado15.idl";
2.1 This avails all definitions inside the imported IDL file ("msado15.idl" in above example) inside the current IDL file.
3. An example use of the [importlib] directive is listed below :
library MyInterfacesLib
{
importlib("msado15.tlb");
...
};
3.1 This avails all types that have already been compiled in another type library ("msado15.tlb" in the above example) to the current IDL file.
3.2 Note that an [importlib] statement can only appear inside a [library] statement. Furthermore, the imported type library, together with the generated type library of the current IDL file, must be available at runtime for the application.
3.3 Now, unlike the [import] statement, where a #include <header file of the imported IDL> directive is generated inside the .h file the current IDL, an [importlib] statement does not cause the generation of such an #include statement.
3.4 Because of this, the cpp_quote IDL keyword should be used to #include any necessary header files in the generated .h file for the current IDL. For example :
cpp_quote("#include \"msado15.h\"")
|
|
|
|
|
1. The CCmdTarget class implements COM interfaces via the declaration of macros. These include DECLARE_INTERFACE_MAP(), BEGIN_INTERFACE_MAP(), INTERFACE_PART() and END_INTERFACE_MAP().
2. CCmdTarget also implements its own version of QueryInterface() named InternalQueryInterface().
3. In order to be generic, InternalQueryInterface() is coded with the help of the interface map which is accessed via the function GetInterfaceMap(). The interface map itself is an array of AFX_INTERFACEMAP_ENTRY structures. Each AFX_INTERFACEMAP_ENTRY structure indicates a specific COM interface that the CCmdTarget class exposes.
4. InternalQueryInterface() looks up this array of AFX_INTERFACEMAP_ENTRY structures to determine whether an interface is supported.
|
|
|
|
|
1. The API to use for connecting to the event source of an object to a sink in a client application is AfxConnectionAdvise().
2. Assuming an event source interface that is purely IDispatch-based, we can implement an event sink (using MFC) via a CCmdTarget-derived class.
3. Listed below are the basic requirements of a sink :
3.1 It must implement IDispatch.
3.2 It must acknowledge the support of the DIID (dispatch ID) of the outgoing event interface.
3.3 It must implement the methods of the outgoing source interface.
4. Based on the requirements listed above, we can proceed to create an eventsink that is based on code provided by MFC. The following are the salient points of such a class :
4.1 It must derive from CCmdTarget. This is due to the CCmdTarget class' internal support of the IDispatch interface (as per requirement 3.1). The CCmdTarget's IDispatch implementation is achieved via a tiny embedded class named XDispatch. An instance of this emdedded class (m_xDispatch) is declared inside CCmdTarget. m_xDispatch must first be initialized via the CCmdTarget::EnableAutomation() function. This should be called inside the constructor of the sink class.
4.2 It must include the DECLARE_INTERFACE_MAP() macro in its class definition. This indicates to the MFC framework that this class will have a custom interface map. Through the interface map, we declare the various interfaces (including any source Dispatch interface) supported by the sink class (as per requirement 3.2). Via the macros BEGIN_INTERFACE_MAP(), INTERFACE_PART() and END_INTERFACE_MAP(), we declare that the sink class implements the source Dispatch interface.
4.3 It must include the DECLARE_DISPATCH_MAP() macro in its class definition. This macro indicates to the framework that this class will provide a dispatch map to expose IDispatch-based methods and properties. Through the diapatch map, we declare the various methods and properties exposed by the sink class. The methods must of course correspond with the methods of the source IDispatch interface (as per requirement 3.3).
The macros used are : BEGIN_DISPATCH_MAP(), DISP_FUNCTION(), DISP_PROPERTY(), END_DISPATCH_MAP().
5. In a call to AfxConnectionAdvise(), the 3rd parameter must be the IUnknown interface pointer to the event sink. To get this IUnknown interface pointer, the entry point function is the CCmdTarget::GetIDispatch() function. Call this function using the sink object. From the IDispatch interface pointer returned from CCmdTarget::GetIDispatch(), we can QueryInterface() it for an IUnknown interface pointer.
modified on Tuesday, June 16, 2009 7:11 AM
|
|
|
|
|
To determine whether an ActiveX is currently in Design Mode or in Runtime Mode, use the CComControl::GetAmbientUserMode() ATL function.
|
|
|
|
|
Note the following about asynchronous interfaces :
1. An asynchronous interface must only derive from IUnknown directly.
2. If an asynchronous interface is derived from another interface that derives from IUnknown, calls to an asynchronous method may not go correctly (e.g. a different method may end up getting called).
3. If an asynchronous interface is derived from IDispatch or is derived from another IDispatch-derived interface, it will not be possible to obtain the asynchronous interface from ICallFactory::CreateCall().
|
|
|
|
|
If a COM object (written in C++) is to implement 2 interfaces with a common name and common number of parameters and parameter types, then the following are options :
1. Use interface shims to supply 2 separate implementations of the 2 methods.
2. Supply only one method. This way, both interfaces share one implementation for the common method.
If a COM object (written in C++) is to implement 2 interfaces with a common name but different number of parameters or different types of parameters , then the following assertion holds :
1. The C++ compiler will properly sort out the appropriate method for each interface's vtbl. In other words, nothing need be done. The C++ compiler will ensure that the correct method will be called according to which interface is used.
|
|
|
|
|
Note the following findings about asynchronous interfaces :
1. An interface that is marked to be asynchronous must be derived from IUnknown. It cannot be derived from IDispatch.
2. A C++/ATL implementation which has #import'ed the originating IDL may experience compilation errors e.g. "MIDL2320 : conflicting optimization requirements, cannot optimize : [Procedure '...']" :
2.1 The cause of the problem has to do with the fact that the asynchronous interface most likely was declared outside of the "library" statement in its original IDL file.
2.2 This indicates to the compiler (compiling the current implementation) that the asynchronous interface will not be using Type Library marshaling (i.e. standard marshaling that uses the Type Library for information instead of any proxy/stub DLL).
2.3 The marshaling for the asynchronous interface will be done via a proxy/stub DLL.
2.4 We currently do not why the compilation error is issued. However, we do have a soluton : place an "importlib" statement inside the "library" statement of the current implementation. It must import the type library of the original IDL file.
2.5 This will avert the compilation problem.
3. A good news about asynchronous interfaces is that it can be implemented in VB. However, the VB implementation must be ActiveX EXE based.
|
|
|
|
|
* If you define an interface in a Type Library and implement it in Visual Basic, no problem.
* For example, if you define an interface ITestInterface in a Type Library and then have your VB ActiveX project reference this type lib, the following statement goes fine :
Implements ITestInterface
* However, if you define a source (i.e. event) interface in a Type Library and try to have a Visual Basic ActiveX raise its events via RaiseEvent statements, not possible.
* For example, if you define a dispinterface _ITestEvents with event Event01() in a Type Library and then have your VB ActiveX Project raise it as follows :
RaiseEvent Event01()
The VB Compiler will complain of an unidentified event.
If you try to raise it as follows :
RaiseEvent <typelibraryname>.Event01()
The compiler will not complain but it still won't work. The problem is that _ITestEvents will not be recognized as being a source interface of the ActiveX.
* If we try to define the events of _ITestEvents in the declaration of the ActiveX itself, e.g. :
Public Event Event01()
The ActiveX will then declare a source interface for itself and Event01() will be absorbed into this default source event set, i.e. you will find the following declarations when you observe the TLB generated for the ActiveX :
coclass clsVBActiveX
{
...
...
...
[default, source] dispinterface __clsVBActiveX;
};
...
...
...
dispinterface __clsVBActiveX
{
void Event01();
};
|
|
|
|
|
See the MSDN topic "Visual Basic Component Shutdown Rules" for more details.
One important point to note is that if a VB-written COM object creates or simply loads any forms, then this form must be unloaded in order that the VB-written COM object be able to shut down properly.
|
|
|
|
|
1. COM Spy console that allows you to spy applications using COM interfaces:
http://www.nektra.com/products/deviare/comconsole/index.php
The COM Spy application is based on Deviare hook library so all you can see in the console can be done by code from any language supporting COM. Source code of this application is available.
2. Another one :
http://staff.develop.com/jasonw/comspy/default.htm
3. Yet another open sourced COM spy :
http://jacquelin.potier.free.fr/winapioverride32/
|
|
|
|
|
Using a CRITICAL_SECTION objects inside VB app may cause a crash in MSVBVM60.dll. The VB app that I created was a COM object.
Reasons unknown at this time (circa November 2008).
|
|
|
|
|
If you write an ActiveX control in ATL, then if you specify that your ActiveX control will fire a non-default event, a VB client application will not be able to receive this event.
The reason for this is that a VB-written client application will only connect with the -default- event set of the ActiveX control.
The default event set is that which is specified in the ATL ActiveX control's class declaration, specifically the IProvideClassInfo2Impl template instance declaration :
IProvideClassInfo2Impl<[clsid], [iid of default outgoing dispinterface], ...>
Here, the second parameter is used to specify the IID of the default outgoing dispinterface.
If one needs the VB client app to connect with a different event set, then modify this IID accordingly.
modified on Sunday, November 30, 2008 8:45 AM
|
|
|
|
|
Q : For marshelling between apartments, are the stub/proxy code generated automatically by COM Runtime ? Is thre a need for us to explicitly write stub/proxy code by ourselves ?
A : If your interface methods and properties strictly uses types that are automation-compatible (i.e. the types that can be found in a VARIANT structure), then the standard proxy and stub code as contained in OLEAUT32.DLL (the standard Proxy/Stub DLL for Ole Automation) will be used. Yes, in this case, there is no need to explicitly program proxy/stub code.
However, please take note of the following :
1. The type library containing your interface definitions must be registered. This type library will be loaded by OLEAUT32.DLL at runtime in order to understand the signatures of your methods (parameter types, return types) for correct marshaling/unmarshaling and method calls across apartments.
2. The interface definitions involved in marshaling must be contained in the type library. In order for this to happen, the interface definition must either be defined inside the "library" statement in the source IDL file, or it is at least referenced inside the "library" statement. Otherwise, marshaling will fail. One of the possible failure code is TYPE_E_ELEMENTNOTFOUND (0x8002802BL).
3. Most importantly, the interface definition must be attributed with either "oleautomation" or "dual" or that it is by definition a "dispinterface". This will ensure that the Type Library Marshaler (i.e. OLEAUT32.DLL) recognizes the interface as being OLE-AUTOMATION compatible. Failure to comply will result in failure in marshaling. One of the possible failure code is E_FAIL (0x80004005L).
|
|
|
|
|
A few days ago, I created an ATL (with MFC support) DLL project. The project compiled properly with absolutely no problem.
Then today, after doing some code modifications (which, believe me, were trivial and innocuous), I was confronted with the link error LNK1169 : Multiple Symbols Defined.
The problem was that while linking with mfcs42.lib, the linker noticed that this library contains a DEFINITION (i.e. full code, and not just a reference) of the function _DllMain@12. However, it also noted that the same function _DllMain@12 was already defined inside MSVCRTD.lib.
After some research and testing, I discovered the following facts regarding DllMain() when used in the context of a DLL project in which MFC is used :
1. Because MFC is used, a CWinApp class will be generated. This is so that the DLL application startup and shutdown processes can be abstracted by the CWinApp class (e.g. the use of InitInstance() and ExitInstance(), etc).
2. For this to happen, MFC must have its own version of DllMain() which will initialize the CWinApp class etc. Hence, this (MFC's) DllMain() must be the one eventually used (i.e. linked-to) by the Dll.
3. The MSVC runtime libraries also include their own versions of DllMain(). This being the case, potential conflicts can happen.
4. Such multiple-defined symbol link errors normally do not cause any problems because the linker will use the function that it finds first in its list of libraries.
5. It is still unclear to me why this link error occurred but this is a blessing in disguise because had this error not appeared, the DllMain() defined inside MSVCRTD.LIB would have been used and there will be problems : i.e. my CWinApp class will not be properly initialized.
Solution
---------
The solution to the above problem is to make sure that MFC's DllMain() is discovered first by the linker during linkage.
The way to do this in VC++ 6.0 is to go to the Project Settings, go to the "Link" tab, go to the "General" category, type in the name of the appropriate MFC library (e.g. mfcs42.lib, in my case) in the "Object/library modules" box. If the competing library (i.e. msvcrtd.lib in my case) is also in the box, make sure that the target MFC library is placed BEFORE the competing library.
Note that in my case, simply typing mfcs42.lib in the box ensured that this library is used first. Msvcrtd.lib was not in the box and so I did not have to put it there either.
Hope this helps others.
- Bio.
modified on Friday, May 9, 2008 7:52 AM
|
|
|
|
|
For C++ classes whose objects are stored inside containers like vectors, lists, deques, the following class items are generally required :
1. Constructors - naturally.
2. Copy Constructors - for creating objects that are stored inside the containers. These contained objects are actual copies of the originals that are inserted into the containers.
3. Destructors - naturally.
4. Assignment operator - needed especially when elements in the containers are removed (e.g. via the erase() method).
|
|
|
|
|
If you have written a COM EXE server in ATL, it is possible to change the order of Class Factory registration in the server. Here's how :
1. Look up the ObjectMap declaration. This is encapsulated between the BEGIN_OBJECT_MAP() and END_OBJECT_MAP() macros.
2. Between the BEGIN_OBJECT_MAP() and END_OBJECT_MAP() macros are listed lines of OBJECT_ENTRY() statements.
3. The order of this list affects the order of Class Factory registration.
4. For example, in the following sample code :
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_TestCOMExeObject01, CTestCOMExeObject01)
OBJECT_ENTRY(CLSID_TestCOMExeObject02, CTestCOMExeObject02)
END_OBJECT_MAP()
The class factory for CTestCOMExeObject01 will be registered first followed by CTestCOMExeObject02.
|
|
|
|
|
Note that the order of registration can be important :
1. In Visual C++ 6.0, if an ATL singleton COM object housed within an EXE server creates another COM object which is housed in the same EXE server, then there would be a problem if the contained object is registered after the singleton object.
2. This is because the singleton object will be created inmediately upon registration. If the registration of the contained object occurs after the singleton, then the singleton object creation will not be creatable and will block until the contained object has been registered, thus causing a deadlock.
- Bio.
|
|
|
|
|
TCHAR szModuleFilePath[_MAX_PATH];
GetModuleFileName
(
_Module.GetModuleInstance(),
szModuleFilePath,
sizeof(szModuleFilePath)
);
where _Module is the CComModule of the COM DLL :
CComModule _Module;
|
|
|
|
|