|
Brian Muth 11/10/2005 12:50 PM PST
"mysteron" <acornatom66@yahoo.com> wrote in message
news:eP1L0yi5FHA.2020@TK2MSFTNGP10.phx.gbl...
>I was reading somewhere that 'every STA needs it's own message pump' ..
>does this also apply to the scenario where an *inproc* object creates a
>bunch of worker threads (and makes those threads STA's by calling
>CoInitalize(NULL)) ... I have a test app here that is an inproc control
>that does just this .. spawns a bunch of worker threads, makes them into
>STA's and marshals interfaces into them from the main STA .. and there's
>only one message pump pumping..
Not true. Each thread lives in its own STA, and each of those has a message
pump as well.
Brian
|
|
|
|
|
Alexander Nickolov 11/10/2005 6:05 PM PST
I assume you mean your worker threads are blocking on an
event or something waiting for work to come. Try running
Word and notice how long it takes to start. The problem is
it is a DDE server and DDE uses broadcast messages.
Since your threads are not dispathing their message queues,
the DDE broadcast hangs (until it times out) waiting for
responses from any top-level windows on a blocked thread.
Note DDE is not the only piece of code to broadcast
messages - open sysedit, edit win.ini and save and this
results in a WM_WININICHANGE message being broadcast
too. (At least that's my memory from Win9x... A quick
check in MSDN Library reveals this has been superseded
by WM_SETTINGCHANGE today, but the principle is the same.)
The moral is: "seems to work" does not equal "works".
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnickolov@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
|
|
|
|
|
By: mysteron In: microsoft.public.win32.programmer.ole
i'm having trouble seeing this hidden window in spy++ ... soes anyone know
if there are any issues that might be preventing me from peeking into this
window and watching the COM calls as they are invoked?
|
|
|
|
|
Subject: Re: OleMainThreadWndClass in spy++ 11/10/2005 9:26 AM PST
By: Brian Muth In: microsoft.public.win32.programmer.ole
Just wondering if you might find COMSpy or COMTrace tools useful for your
project?
http://www.blunck.info/comtrace.html
Brian
|
|
|
|
|
by : mysteron
thanks - i was looking for tools like this... even pondering writing my own
"Brian Muth" <bmuth@mvps.org> wrote in message
|
|
|
|
|
By : Igor Tandetnik.
mysteron <billg@microsoft.com> wrote:
> i'm having trouble seeing this hidden window in spy++ ... soes anyone
> know if there are any issues that might be preventing me from peeking
> into this window and watching the COM calls as they are invoked?
It's a so called message-only window (see CreateWindow, HWND_MESSAGE).
These don't show in Spy++.
--
With best wishes,
Igor Tandetnik
With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925
|
|
|
|
|
You can only see it on Win9x (including Me) and Win NT4
and before. Starting with Win2K COM uses a lightweight
messaging window (a child of HWND_MESSAGE) which you
can't see with Spy++.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnickolov@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
|
|
|
|
|
I'm trying to find some decent documentation about how things work down at
the proxy/stub level in apartment threaded COM ... haven't found much so
far.
Q: anyone know where I can find a really good detailed description of how
all that is implemented?
Q: in a simple scenario, two STA's in a single process, talking to each
other -what exactly is COM doing with message queues and worker threads
inside the proxy/stub? does it boil down to a simple sendmessage from one
thread to the other .. or is it a postmessage? My understanding is that it
is the latter.
Q: this isn't a COM question as such but i'd like your opinion on this ..
two processes A and B are running. They each create a window. At random
intervals they do a sendmessage to the opposite process. Isn't this a clear
invitation to deadlock on windows? Same of course if A and B are threads in
the same process.. right?
TIA
|
|
|
|
|
Reply :
See inline.
--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: agnickolov@mvps.org
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
"mysteron" <billg@microsoft.com> wrote in message
news:utgI$oZ5FHA.632@TK2MSFTNGP10.phx.gbl...
> i'm trying to find some decent documentation about how things work down at
> the proxy/stub level in apartment threaded COM ... haven't found much so
> far.
>
> Q: anyone know where I can find a really good detailed description of how
> all that is implemented?
If you manage to find one, I'd love to get the link as well...
>
> Q: in a simple scenario, two STA's in a single process, talking to each
> other -what exactly is COM doing with message queues and worker threads
> inside the proxy/stub? does it boil down to a simple sendmessage from one
> thread to the other .. or is it a postmessage? My understanding is that it
> is the latter.
Both can be used depending on the interface. Normally COM uses
PostMessage, unless the method specifies the [input_sync] attribute.
Very few interfaces use that, mostly ones dealing with OLE and
ActiveX Documents. Alas, it's very poorly documented, you'll be hard
pressed to find any mention of [input_sync] on Microsoft's site. I used
Google to find links.
>
> Q: this isn't a COM question as such but i'd like your opinion on this ..
> two processes A and B are running. They each create a window. At random
> intervals they do a sendmessage to the opposite process. Isn't this a
> clear invitation to deadlock on windows? Same of course if A and B are
> threads in the same process.. right?
No, there's no deadlock. IIRC, SendMessage spins a modal loop
after posting the message to the target thread's message queue.
I believe the loop does not dispatch all messages, but it does let
through other SendMessage calls. You should ask this on a UI
group where the experts are.
>
> TIA
>
|
|
|
|
|
An email from krssagar :
I have a Connection point interface say ...(using ATL on VC++ 7)
dispinterface _MYEvents
{
properties:
methods:
[id(1), helpstring("method MyEvent")] HRESULT MyEvent([in]BSTR bstStr,[in] ISomeInterface *pInter);
};
a VC++ MFC client application registered for the event and created the
handler as :
HRESULT __stdcall MY_CPPDlg::MyEvent(CComBSTR bstStr,ISomeInterface *pInter)
{
...
...
...
pInter->Release(); //Is this LEGAL ?
}
who is resposible for releasing the interface(ISomeInterface) ?
COM or the Application ...?
When I addref the interface before firing to the client but ..for C#
and VB clients shows there is a memory leak !!
|
|
|
|
|
Reply from Bio :
Hello krssagar,
The rules of COM states that if an interface pointer is returned from a method (i.e. it is an [out] parameter, which means that the parameter would be typed something like ISomeInterface**), the method implimenter must AddRef() it and the client must later Release() it.
However, in your case, the "pInter" inerface pointer of MyEvent() is an [in] pointer which means that it is passed by value and not supplied as a reference. Hence, it should not be AddRef()'ed and the event handler should not call Release().
- Bio.
|
|
|
|
|
|
Take a simple COM coclass declaration in VC7.0 using ATL Attributes :
[
coclass,
threading("apartment"),
support_error_info("ISimpleCOMObject3"),
event_source("com"),
vi_progid("SimpleCOMObject3.SimpleCOMObject3"),
progid("SimpleCOMObject3.SimpleCOMObject3_imp.1"),
version(1.0),
uuid("E11C9A28-EFD2-45F0-979F-15C749AEB1A0"),
helpstring("SimpleCOMObject3 Class")
]
class ATL_NO_VTABLE CSimpleCOMObject3 : public ISimpleCOMObject3
{
...
...
...
__event __interface _ISimpleCOMObject3Events;
...
...
...
};
By declaring the following CSimpleCOMObject3 :
* event_source("com") attribute
* __event __interface _ISimpleCOMObject3Events
An IConnectionPointImpl is generated by the ATL Attributes Wizard.
There is thus no need to declare our own Proxy for event firing e.g. :
CProxy_ISimpleCOMObject3Events<csimplecomobject3>
from the following template :
template <class t="">
class CProxy_ISimpleCOMObject3Events : public IConnectionPointImpl<t, &diid__isimplecomobject3events,="" ccomdynamicunkarray="">
Later, if we need to refer to the collection of Connection Points clients, we refer to the following "m_vec" :
ATL::IConnectionPointImpl<csimplecomobject3, &__uuidof(_isimplecomobject3events),="" atl::ccomdynamicunkarray="">::m_vec
To help simplify using this m_vec, we can declare a synonym for the very long type name :
ATL::IConnectionPointImpl<csimplecomobject3, &__uuidof(_isimplecomobject3events),="" atl::ccomdynamicunkarray="">
by declaring the following typedef :
typedef ATL::IConnectionPointImpl<csimplecomobject3, &__uuidof(_isimplecomobject3events),="" atl::ccomdynamicunkarray=""> CATLProxy_ISimpleCOMObject3Events;
2.2 Thereafter, we can use the following code to reach the m_vec :
CATLProxy_ISimpleCOMObject3Events* pCATLProxy_ISimpleCOMObject3Events = dynamic_cast<catlproxy_isimplecomobject3events*>(this);
int nConnections = (pCATLProxy_ISimpleCOMObject3Events -> m_vec).GetSize();
...
...
...
CComPtr<iunknown> sp = (pCATLProxy_ISimpleCOMObject3Events->m_vec).GetAt(nConnectionIndex);
|
|
|
|
|
Introduction
COM is a truly excellent programming model for the development of integrating components based on interfaces. Some of the fundamental principles of COM have their roots in Object-Oriented Philosophies. It is a great platform for the realization of Object-Oriented Development and Deployment.
One of COM's major contributions to the world of Windows Development is the awareness of the concept of separation of interface from implementation. This awareness has no doubt profoundly influenced the way programmers build systems today.
An extension of this fundamental concept is the notion of : one interface, multiple implementations. By this, we mean that at runtime, a COM client can choose to instantiate an interface from one of many different concrete implementations. Each concrete implementation can be written in any programming language that supports COM component development, e.g. C++, Visual Basic, Delphi, PowerBuilder, etc.
Via COM-interop, a .NET component can also be deployed as a COM component. This implies that a COM interface implementation can be developed in a .NET language like C#. And this is what I aim to expound on and demonstrate in this article : the implementation of a COM interface in C#.
I will assume that the reader already has prior knowledge on the following :
1. COM development in general and IDL in particular.
2. C++ and ATL.
3. C# and various .NET tools like tlbimp.exe and gacutil.exe.
The following is a general outline of how I intend to go about my explanations :
1. We will briefly recap IDL and then define a simple COM interface.
2. We will develope 2 separate concrete implementations of this interface : one in C++ and the other in C#.
3. We will write a client application that will instantiate both concrete implementations and call their methods.
Emphasis will be given to the C# implemetation. I will expound on how a C# component is transformed into a COM component usable by an unmanaged client.
-- modified at 10:30 Thursday 13th October, 2005
|
|
|
|
|
A Short Refresher on IDL
The common practice in COM development is to start the definition of an interface using IDL (interface definition language). On this note, it is important to grasp the intended purpose of IDL and understand how it realizes OO principles. An IDL file is not just "another one of Microsoft's proprietary file types". It deserves deeper understanding.
Although I have stated that I wil assume the reader has prior knowledge of IDL, I will go on just a little anyway to explain here some of the more important concepts behind the IDL and the coclass (an IDL keyword) in particular :
A coclass is COM's (language independent) way of defining a class (class in the object-oriented sense).
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 :
CoCreateInstance
(
CLSID_MyObject,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMyObject,
(void**)&m_pIMyObject
);
is conceptually equivalent to the following C++ code :
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 the COM world. The main feature of the coclass is that it is (1) binary in nature and consequently (2) programming language-independent.
-- modified at 19:18 Sunday 16th October, 2005
|
|
|
|
|
A Simple COM Object Interface
We begin our code development by defining a simple COM interface named ISimpleCOMObject. Using Visual Studio .NET, we create an ATL project named SimpleCOMObject.sln (complete source codes for this project is included in the downloadable sample codes).
This SimpleCOMObject.sln project will not contain any useful implementation codes. Its purpose is simply to allow us to automate the creation and maintenance of the ISimpleCOMObject interface. This is accomplished via the use of the ATL Wizards.
The project file folder also serves as the central point for the storage and generation of various .NET related resources (e.g. Primary Interop Assembly, Strong Name Key Files, etc). More on these later.
Listed below is a fragment of code taken from SimpleCOMObject.idl showing the ISimpleCOMObject interface :
[
object,
uuid(9EB07DC7-6807-4104-95FE-AD7672A87BD7),
dual,
nonextensible,
helpstring("ISimpleCOMObject Interface"),
pointer_default(unique)
]
interface ISimpleCOMObject : IDispatch{
[propget, id(1), helpstring("property LongProperty")] HRESULT LongProperty([out, retval] LONG* pVal);
[propput, id(1), helpstring("property LongProperty")] HRESULT LongProperty([in] LONG newVal);
[id(2), helpstring("method Method01")] HRESULT Method01([in] BSTR strMessage);
};
ISimpleCOMObject contains one property (LongProperty) and one method (Method01()). We stipulate that ISimpleCOMObject is meant to be implemented by an object that takes a long value (LongProperty) and then displays its value when we call Method01(). The BSTR parameter "strMessage" is meant to be a short message which is to be displayed with the LongProperty value when Method01() is called.
We defined ISimpleCOMObject as a dual interface. Hence a client application can call its methods by using a vtable interface pointer or by using an IDispatch interface pointer. Although it is possible to define ISimpleCOMObject as deriving from IUnknown, I have chosen to derive it from IDispatch in order to ensure that parameters and return values for its methods be strictly automation-compatible (i.e. the types that can be stored in a VARIANT structure). This is done to keep things simple.
I will assume that the reader is already well-versed in the implementation of a COM interface using an unmanaged language like C++. It is the implementation in a .NET language like C# that I intend to explain in detail in this article. The use of automation-compatible types will help to keep this process as simple as possible.
Although SimpleCOMObject.sln contains no useful implementation for ISimpleCOMObject, we will nevertheless need to compile it. Once this project is compiled successfully, two important files will be produced :
1. A Type Library (SimpleCOMObject.tlb).
2. A DLL (SimpleCOMObject.dll).
These will be created in the Debug directory of the project's containing folder. If you have downloaded my sample codes, and have not modified anything so far, these files will be stored in the following path :
<main folder>\SimpleCOMObject\interfaces\SimpleCOMObject\Debug
where is wherever you have copied the source codes zip file to.
The SimpleCOMObject.tlb will be used to produce something known as a Primary Interop Assembly. More on this in the next section.
The SimpleCOMObject.dll itself will contain the SimpleCOMObject.tlb (embedded as a binary resource). It is important as it will be registered (by the Visual Studio .NET IDE) as the Type Library for the coclasses and interfaces defined in SimpleCOMObject.idl.
This Type Library registration is done so that COM will know where to look for type information when it needs to perform marshalling for the interfaces defined in SimpleCOMObject.idl. This process is also known as Type Library Marshaling.
-- modified at 19:55 Tuesday 18th October, 2005
|
|
|
|
|
The CreateAndRegisterPrimaryInteropAssembly.bat Batch File
After compiling the SimpleCOMObject.sln
|
|
|
|
|
The following is extracted from :
" HOWTO: Use structs in Automation-compatible interfaces "
<a href="http://www.mvps.org/vcfaq/com/4.htm" rel="nofollow">http://www.mvps.org/vcfaq/com/4.htm</a>[<a href="http://www.mvps.org/vcfaq/com/4.htm" target="_blank" rel="nofollow" title="New Window">^</a>]
Neither IDL nor Automation define byte alignment for a struct. VB assumes 4-byte alignment, while #import in VC assumes 8-byte alignment. For most Automation structs this isn't an issue. However, if you use 8-byte types like double, this may make a difference:
[uuid(...), helpstring(...)]
struct BadAlign
{
long nLongValue;
double dblDoubleValue;
};
For VB, the double field starts at the fourth byte of the struct, while for VC's #import it starts at the eight byte. This poses a significant problem. It can be solved by adding an inject_statement clause to #import:
#import "My.tlb" inject_statement("#pragma pack(4)")
|
|
|
|
|
When setting the property of a COM object via IDispatch::Invoke() using DISPATCH_PROPERTYPUT, it is important to set the following :
* DISPPARAMS.rgdispidNamedArgs must point to a DISPID whose value is
DISPID_PROPERTYPUT (-3).
* DISPPARAMS.cNamedArgs must be equal to 1.
If this is not done, IDispatch::Invoke() will return DISP_E_PARAMNOTFOUND. The following is a sample IDispatch::Invoke() call used to set a long property to value 202:
void InvokeProperty(IDispatch* dispatch)
{
DISPPARAMS dp;
long nError = 0;
long nWarning = 0;
DISPID dPutID;
memset(&dp, 0, sizeof(DISPPARAMS));
dp.cArgs = 1; // Number of arguments to Invoke() remains 1.
// This is important.
dPutID = DISPID_PROPERTYPUT;
dp.rgdispidNamedArgs = &dPutID;
dp.cNamedArgs = 1;
VARIANTARG* pArg = new VARIANTARG[dp.cArgs];
dp.rgvarg = pArg;
memset(pArg, 0, (sizeof(VARIANT))*(dp.cArgs));
V_VT(&(dp.rgvarg[0])) = VT_I4;
V_I4(&(dp.rgvarg[0])) = 202;
VARIANTARG vr;
VariantInit(&vr);
//Finally making the call
UINT nErrArg;
HRESULT hr = S_OK;
hr = dispatch -> Invoke(0x03, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dp, &vr, 0, &nErrArg);
return;
}
By the way, I also checked up MFC's implementation codes for
COleDispatchDriver. It does the same thing, i.e. set
DISPPARAMS.rgdispidNamedArgs to DISPID_PROPERTYPUT and DISPPARAMS.cNamedArgs
to 1 before calling Invoke().
|
|
|
|
|
Sample code can be found in Develope\COM\DispatchAPI\kenB.
|
|
|
|
|
The following information is gotten from mariabl's blog (http://blogs.msdn.com/mariabl/archive/2005/07/21/441664.aspx[^]) :
At various times I've been annoyed with the way MIDL will mangle enum names. For example, if you have this in an IDL file:
typedef enum
{
value1,
value2
} MyEnum;
...if you run MIDL on it, in your .h file you'll get something like:
typedef
enum __MIDL___MIDL_itf_test_0000_0001
{
value1 = 0,
value2 = value1 + 1
} MyEnum;
The part that bugs me is the part in red. (I've run across various inconveniences with it when using .NET interop and even in C++.)
All you have to do is specify that tag (name) yourself!
So if I instead put in my IDL file:
typedef enum MyEnum
{
value1,
value2
} MyEnum;
I get in my .h file:
typedef
enum MyEnum
{
value1 = 0,
value2 = value1 + 1
}
MyEnum;
It seems that in some cases (going through the process of TLBs, .NET interop, etc.) the tag is used and sometimes the typedef name is used, and sometimes you get messed up if you have different tags because you've imported the same IDL file in two different places. So if you just use the same name for both you won't have weird problems later.
I guess everyone except me already knew this, but I just thought I'd pass it on in case someone didn't!
|
|
|
|
|
Recently, a CP member named LongIsland wrote the following post in the ATL forum :
--- START ---
Hello. I am new to ATL and had a serious problem, see I want to use interface A and interface B, both from MS. The bad thing is they all have method named "GetWindowHandle", what should I do ???!!!
I tried to modify one of them's header to just make it compile, but it just sucks, I think there must be a decent solution.
Thanks a lot ahead.
--- END ---
It was replied by Jörgen Sigvardsson. See next message.
|
|
|
|
|
Jörgen Sigvardsson's response :
--- START ---
Assuming that you want to return different things for each GetWindowHandle, you can do this:
template <typename T>
class InterfaceAShim : public InterfaceA
{ STDMETHOD(GetWindowHandle)(HWND* phwnd) { return static_cast<T*>(this)->InterfaceA_GetWindowHandle(phwnd); }};
class YourClass : public InterfaceAShim<YourClass>, public InterfaceB
{
...
HRESULT InterfaceA_GetWindowHandle(HWND* phwnd)
{ // do your stuff here for InterfaceA::GetWindowHandle() }
STDMETHOD(GetWindowHandle)(HWND* phwnd)
{ // do your stuff here for InterfaceB::GetWindowHandle() }
};
If you are going to return the same handle in both methods, you don't have to do anything.
--- END ---
|
|
|
|
|
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 ---
|
|
|
|
|