Interop COM(-isch) interface marshaling results in

2019-03-02 22:47发布

问题:

I am trying to write a managed interop library for a native C++ plugin standard. This native C++ library uses a COM-compatible interface design. It does NOT however, do any of the class registration stuff. As with COM, all interfaces derive from IUnknown (called FUnknown but same 3 methods anyway).

I have written a simple C++ console app that loads my managed test plugin and retrieves the initial (root) interface (an object factory pattern - much like com) through an exported method. I use a 3rd party DllExport code attribute implementation - that does seem to work fine. The C++ test app uses LoadLibrary/GetProcAddress and successfully retrieves a reference to the interface. I can set a breakpoint in my managed exported function and it gets hit as expected.

Then the C++ test app calls AddRef on the IUnknown (part of the) interface and it returns 2 - as could be expected. Note that my managed interface definition (counter-part) does NOT derive from IUnknown -or include these methods. I would say that means that the managed marshaling magic has stepped in and provided a CCW.

Then the C++ test app calls a simple method on the factory interface - one that just returns an int32 - and that also arrives in the managed implementation (breakpoint gets hit), but as that method returns it throws an AccessViolationException - somewhere in the managed-unmanaged transition.

class IPluginFactory : public FUnknown
{
public:
    // removed other methods before and after this one

    virtual int32 PLUGIN_API countClasses () = 0;       
};

The int32 is a #define and the PLUGIN_API gets defined as __stdcall - which is COM compatible as far as I can tell.

The managed representation of that interface I have defined as follows:

[ComImport]
[Guid("same guid as in C++ file")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPluginFactory
{
    [return: MarshalAs(UnmanagedType.I4)]
    Int32 CountClasses();
}

The managed implementation of this method just returns a hard coded number (1).

I have tried many things (so many I can not even remember them all) and am currently at a loss as how to resolve this, or what the problem could be.

Any help is much appreciated. Thanx!

EDIT: Request for details on FUnknown:

class FUnknown
{
public:
    virtual tresult PLUGIN_API queryInterface (const TUID iid, void** obj) = 0;
    virtual uint32 PLUGIN_API addRef () = 0;
    virtual uint32 PLUGIN_API release () = 0;
};

回答1:

We can't see what FUnknown looks like. It must be identical to IUnknown to allow the interop to work. The CLR will make calls to AddRef, Release and QueryInterface automatically. And it is very important that there are exactly three methods in FUnknown, if FUnknown has more or less then you'll end up calling the complete wrong method when your C# code calls CountClasses(). Indeed a good way to trigger an AVE.

One problem that we can see is that CountClasses is not COM compatible, methods must return an Int32 that is the HRESULT. An error code, a value that isn't zero automatically triggers an exception in the C# program. Incompatible method signatures are supported, you have to use an attribute. Like this:

[PreserveSig]
int CountClasses();

This mistake is otherwise not sufficient to explain the AVE. An interface mismatch is your likelier nemesis. In which case you need a C++/CLI wrapper.