1

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;
};
obiwanjacobi
  • 2,413
  • 17
  • 27

1 Answers1

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I have added FUnknown details to the original question. I AM calling addRef successfully (handled by the wrapper) - so I personally don't think that is the problem. I think I have tried the PreserveSig - but will try again. I was hoping I would not need a CLI wrapper.. :-( – obiwanjacobi Oct 19 '13 at 13:03
  • Hmm, looks like that PreserveSig fixed it! Thank you very much! – obiwanjacobi Oct 19 '13 at 13:06
  • Thinking more on that signature problem. Does this mean I can model a COM method that looks like this HRESULT SomeMethod(ISomeInterface** returnValue) like ISomeInterface SomeMethod() in managed C#? Will the wrapper convert that? How do I indicate an error? Throw an Exception? – obiwanjacobi Oct 19 '13 at 14:45
  • Yes. Your C++ code still uses the original declaration with the HRESULT. You indicate an error by returning a negative HRESULT. Favor using the standard COM error codes listed in the WinError.h, lots of them map to corresponding .NET exceptions. Like __HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY) will trigger OutOfMemoryException. – Hans Passant Oct 19 '13 at 15:03
  • I meant the other way around. ;-) I am writing the managed part and I do not have any say over the unmanaged part (the application where plugin runs in). So in the managed world I indicate an error by throwing an Exception - a type that has a Com HRESULT code counterpart? – obiwanjacobi Oct 19 '13 at 16:40
  • If you can't change the C++ code then the bucks stops there, your C# declaration must match. And no, it is the CLR that throws an exception from the returned HRESULT. Throwing exceptions in C++ code is illegal, there's no interop mechanism for them. You'll just get an undiagnosable and unrecoverable SEHException in the C# program. – Hans Passant Oct 19 '13 at 16:49