views:

763

answers:

3

I have a COM class CMyCOMServer implementing IMyInterface in one application, both with correct GUIDs. CMyCOMServer::QueryInterface will return S_OK (and cast itself to the right type) if IUnknown or IMyInterface is requested, otherwise it returns E_NOINTERFACE.

In another app on the same PC, I call:

HRESULT hr = ::CoCreateInstance(__uuidof(CMyCOMServer), 0, CLSCTX_SERVER,
 __uuidof(IMyInterface ),(void **)&pInterface);

It returns E_NOINTERFACE. So I assumed I was doing something wrong and added a breakpoint on CMyCOMServer::QueryInterface. I found that when CoCreateInstance is called, QueryInterface is triggered several times for different interfaces:

  • First, IUnknown is requested - no problem
  • Then, several interfaces like IMarshall etc are requested... these are not supported so E_NOINTERFACE is returned
  • Finally, IMyInterface is requested. I verify QueryInterface returns S_OK and sets (IMyInterface *)this as the interface pointer, as expected

So my confusion is why the calling CoCreateInstance is leaving me a NULL pointer and return code of E_NOINTERFACE, when the COM server app is clearly returning the interface I ask for?

EDIT: my client app calls CoInitialize(NULL) at startup, this makes no difference.

+2  A: 

Could this be the threading model problem that Raymond Chen wrote about?

Edit in reply to the comment:

If your threading model is incompatible with the threading model of the object you're creating, then COM marshalling kicks in. And if the marshalling stuff isn't there, the error that comes out is E_NOINTERFACE, because the marshalling interface is missing.

It's more about threading models than about marshalling, really.

Thomas
Maybe, but I've never ever used Marshalling and as far as I'm aware after working on this project for 3 years, with well over 100 COM server EXE/DLLs. no custom marshalling or fancy threading stuff is done anywhere. So I'm confused why it should be a problem now.
John
+4  A: 

This happens because COM subsystem tries to marshal your custom interface (IMyInterface) and simply has no idea how to do that. That happens either because the server is out-proc or because the server is in-proc and the thread of the consumer application that calls CoCreateInstance() has called CoInitialize()/ CoInitializeEx() incorrectly so that "multithreaded apartment" is requested as mentioned in the article user Thomas refers to in the other answer.

If you only need an in-proc server you could suppress marshalling by ensuring that the thread calling CoCreateInstance() either calls CoInitialize() or CoInitializeEx() with COINIT_APARTMENTTHREADED to enforce "single-threaded apartment".

If you need an out-proc server you can't get around marshalling. In the latter case you could do one of the following:

  • implement IMarshal - least preferable
  • add proxy/stubs and register them for your custom interface
  • (not sure if it will work for out-proc, but it's the simplest) if your interface can be marshalled with automation marshaller simply include a typlib into the resources of your COM server and register that typelib in the registry.
sharptooth
I have no idea what that means! I suspect if I did, my problem might be solved.
John
Have you read that article?
sharptooth
Yes I have now. it looks relevant but doesn't give me any ideas what to change. The server app has been running for years and I just threw together a new client MFC app to interact with it. I din't see any mention of how to 'suppress marshalling' in that article.
John
The key is how the CoInitializeEx()/CoInitialize() is called by the client. It should call it to set up "apartment" - either call CoInitialize() or CoInitializeEx() with COINIT_APARTMENTTHREADED.
sharptooth
If you don't enforce "apartment" marshalling will kick in. The setting is done saparately for every thread of the client.
sharptooth
I included the above into the answer.
sharptooth
TLB/OLE automation marshalling works fine for cross-process marshalling.
Kim Gräsman
A: 

If your COM server is running in a different process, or a different apartment in the same process, COM needs to know how to package and transmit parameters when you make calls to your interface. This process is called "marshaling".

If you define a custom interface, you need to implement marshaling for it using one of the following approaches.

  • Standard marshaling: have the MIDL compiler to generate a proxy and stub which you must register on the system. This is probably the best option since you have already defined your interface.
  • OLE Automation marshaling: you define an automation compatible custom interface and use the marshaller which is already part of the COM framework
  • Custom marshaling: you implement the methods of IMarshal

When you are debugging your COM server, although you see that you are returning your custom interface in the call to QueryInterface, it does not make it across the process boundary because COM cannot figure out how to marshal that interface, hence the client sees E_NOINTERFACE.

UPDATE (based on your comment)

If this is an existing COM server app then you probably already have a proxy/stub. You need to register this on both the client and server. Could it be that you were testing this on a new machine(s) and you simply forgot to register this? To register you simply do regsvr32 on the proxy/stub dll.

DSO
As I said, the server app is not new code. It's already used in a live system - no changes to the interface or anything. Changing anything on the server side is not an option, I need to make my new client app work with what's there since other clients somehow manage to do so.
John
I think this is the best answer. it turns out we DID have a proxy-stub DLL, I just wasn't aware of it. Using regsvr32 fixed it.
John