views:

323

answers:

2

I've been battling with VStudio, Google and various other tools and websites all day and found no solution - HELP!!

I have two COM interfaces (pure COM, no ATL):

IMyClassFactory and IMyClass with corresponding implementations

I want to use them from C# BUT without registering the COM server with regsvr32. I expose the Class Factory with CoRegisterClassObject and I can successfully create objects of IMyClass with CoCreateInstance from unmanaged code.

So the C# interop...

I created a .NET wrapper with tlbimp myComServer.tlb and loaded it as a reference to my C# client.

Then, when I try to create an instance of IMyClass, I get:

An unhandled exception of type 'System.InvalidCastException' occurred in COMTestClient.exe

Additional information: Unable to cast COM object of type 'MyComServerLib.MyClass' to interface type 'MyComServerLib.IMyClass'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{9F8CBFDC-8117-4B9F-9BDC-12D2E6A92A06}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

Now, I have added traces to the QueryInterface and the only cases when I return E_NOINTERFACE are when it requests for any of Marshal-related interfaces or for IManagedObject.

How do I fix this??

EDIT: My IDL file:

import "unknwn.idl";

[
    object, 
    uuid(...), 
    nonextensible,
    pointer_default(unique)
]
interface IMyClass : IUnknown
{
    HRESULT(SetFirstNumber)(long nX1);

    HRESULT(SetSecondNumber)(long nX2);

    HRESULT(DoTheAddition)([out,retval] long *pBuffer);
};

[
    uuid(...)
]
library MyLib
{
    importlib("stdole2.tlb");

    [
     uuid(...)
    ]
    coclass IMyClassImpl
    {
     [default] interface IMyClass;
    };
}
+4  A: 

You need to either allow your interface to be marshalled (i.e., by marking it as not "local" in the .idl file so that it ends up in the type library, and in the proxy/stub), or aggregate the free-threaded marshaller if you go that way.

To aggregate the FTM, I did something like this:

#define DECLARE_FTM() \
protected: CComPtr<IUnknown> _m_Marshal; \
DECLARE_GET_CONTROLLING_UNKNOWN() \
public: HRESULT FinalConstruct() \
{ return CoCreateFreeThreadedMarshaler(GetControllingUnknown(),&_m_Marshal); }

#define COM_INTERFACE_ENTRY_FTM() COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal,_m_Marshal.p)

Then, in your COM map:

BEGIN_COM_MAP(Blah)
 COM_INTERFACE_ENTRY(IBlah)
 COM_INTERFACE_ENTRY_FTM()
END_COM_MAP()
DECLARE_FTM()

I note that you aren't using ATL and the like - you would need to modify this so that your QueryInterface implementation returns the FTM pointer when IMarshal is queried for.

Note that aggregating the FTM is not something that can really be done lightly - it makes a number of unsafe assumptions that are not always valid. For instance, your class cannot make use of any interfaces which are not themselves free-threaded.

The other alternative is basically as @[Franci Penov] said, you need to ensure that your interface is able to be marshalled. The way I understand it, there is a standard marshaller which is able to marshal any interface in the type library, or you (that is the midl compiler does it more or less automatically) can make a proxy/stub dll (or merge the code for the proxy/stub into your own dll) which is able to marshal it for you.

This article here describes the process of building and registering the proxy/stub in more detail.

1800 INFORMATION
Can you add a bit more info on how to do either solution please? I'll edit my original post with my IDL file. Thanks
everwicked
I've added some more detail
1800 INFORMATION
Does the COM object's reference counting implementation also need to be thread-safe, using Interlocked operations? If the CLR treats a COM object as free-threaded, it will call Release on it from the finalizer thread.
Daniel Earwicker
There is no doubt about that - you will need to ensure that all accesses are protected
1800 INFORMATION
I notice you say that you mark the interface as non-local in the IDL file and have this all done for you, right? What is the declaration for the "non-local"? Using oleautomation like Franci describes doesn't work.
everwicked
I think you need to make sure your proxy/stub dll is properly registered. If you are using ATL, and you create the project using the wizard, it can create one for you, or "merge" the proxy/stub into your project. I'm a bit hazy on the exact details of how to do it in the absense of ATL - this article might help you though: http://msdn.microsoft.com/en-us/library/ms688707%28VS.85%29.aspx
1800 INFORMATION
The declaration for "non-local" is basically to not specify "local" for the interface, which you didn't do, so don't worry about that
1800 INFORMATION
+1  A: 
  1. Move your interface into the library section. This will get its definition in the type library.
  2. Mark your interface as oleautomation. This will mark it as an interface that can be marshalled by using the standard marshaller and the typelib info, as opposed to the proxy/stub generated by the midl compiler. (NOTE: while oleautomation does come from the old OLE world, it does not require your interface to derive from IDispatch)
Franci Penov