|
LongIsland further responded with :
--- START ---
Hi, Jörgen:
Thank you so much, just 3 small questions:
1. Then the best bet in my code is Not use a method name same as common interface method, like GetWindowHandle, IsDirty, and so on. Right? Otherwise, it is tedious to write this kind of wrapper. (Is it the only solution? I think so)
2. why do you use static_cast, since static_cast do not do run time check, there is no difference with reinterpret_cast here.
3. Here:
template
class InterfaceAShim : public InterfaceA {
STDMETHOD(GetWindowHandle)(HWND* phwnd) { return static_cast(this)->InterfaceA_GetWindowHandle(phwnd); }
};
I think inside InterfaceAShim, I need only declare one duplicate method name OR I have to declare all methods from InterfaceA, just want to confirm.
Thanks again, you are ATL guru.
--- END ---
|
|
|
|
|
Jörgen Sigvardsson further responded :
--- START ---
LongIsland wrote:
1. Then the best bet in my code is Not use a method name same as common interface method, like GetWindowHandle, IsDirty, and so on. Right? Otherwise, it is tedious to write this kind of wrapper. (Is it the only solution? I think so)
AFAIK, there is no automated process for this. But I wouldn't say it's the best approach either. I mean, if your method names become odd looking and non descriptive, it's not good.
LongIsland wrote:
why do you use static_cast, since static_cast do not do run time check, there is no difference with reinterpret_cast here.
Because I'm doing an "upcast". T is a subclass of the template. Draw a picture! Thus I don't have to do any casts, because the pointer types are related.
LongIsland wrote:
I think inside InterfaceAShim, I need only declare one duplicate method name OR I have to declare all methods from InterfaceA, just want to confirm.
You only need to declare the duplicate names. Unbound names are deferred to the next subclass, which is your COM class.
--- END ---
|
|
|
|
|
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
|
|
|
|
|