I'm struggling to work out how to access a COM interface provided by a C++ application and use it from a C# .NET application.
I try to access my COM object (which is provided by a running process) from my C# app like this:
object obj = Marshal.GetActiveObject("MyLibrary.Application");
MyLibrary.IMainApp app = (MyLibrary.IMainApp)obj;
The value of obj
is non-null, but when I try to cast it I get System.InvalidCastException
.
I tried implementing a custom wrapper and also linked against a DLL produced by TlbImp.exe
. Both produced the same exception.
There seems to be a lot of documentation regarding interop, but it's not making sense to me -- mostly because I'm not COM-savvy (I am stuck maintaining code that someone else developed), and am entering the scary world of C#, .NET and WPF by diving headlong into it.
Here's what I have on the C++ end....
IDL File
Actually, the extension is .odl
. I don't know if that's a different thing. Note that I've replaced the GUIDs with placeholders like GUID-1
, GUID-2
etc...
import "oaidl.idl";
[ uuid(GUID-1), helpstring("My Type Library"), version(1.1) ]
library MyLibrary
{
importlib("stdole32.tlb");
// Primary dispatch interface for CMainApp
[ uuid(GUID-2) ]
dispinterface IMainApp
{
properties:
[id(1)] BOOL Mode;
methods:
[id(2)] void Quit();
[id(3)] void LogEventMessage(BSTR szMessageType, BSTR szMessage);
[id(4)] BOOL GetNoteDetails(BSTR szQualifiedName, BSTR* lpbstrOperator, DATE* lpdtDate);
};
// Class information for CMainApp
[ uuid(GUID-3) ]
coclass Application
{
[default] dispinterface IMainApp;
};
};
Example Usage
When another C++ application wants to call a function (eg Quit
), they do this:
CLSID clsid;
HRESULT hr;
LPUNKNOWN lpUnk;
LPDISPATCH lpDisp;
BOOL bReturn = FALSE;
// Get Class ID
if (CLSIDFromProgID( OLESTR("MyLibrary.Application"), &clsid ) == S_OK )
{
// Get Active Object from ROT
if ( ( hr = GetActiveObject( clsid, NULL, &lpUnk ) ) == S_OK )
{
hr = lpUnk->QueryInterface( IID_IDispatch, (LPVOID*)&lpDisp );
lpUnk->Release();
if ( hr == S_OK )
{
CMainApp oMainApp;
oMainApp.AttachDispatch( lpDisp );
TRY
{
oMainApp.Quit();
bReturn = TRUE;
}
CATCH( CException, e )
{
bReturn = FALSE;
}
END_CATCH
}
}
}
The class CMainApp
seems to have been auto-generated by the IDL compiler. That file is compiled into the project that wants to use it, and basically does a whole lot of InvokeHelper
calls. I'm happy enough to accept this is just the way it works, and I hope that C# is less convoluted.
Attempt to wrap in C#
I followed an MSDN guide on COM interop in C#. It suggested I could use TlbImp.exe
but that threw up enough warnings to put me off. Besides, I don't want to distribute another DLL. So I took the other approach of writing a wrapper.
This is what I wrote:
using System.Runtime.InteropServices;
namespace MyLibrary
{
// Declare MainApp COM coclass
[ComImport, Guid("GUID-3")]
class MainApp
{
}
[Guid("GUID-2"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
interface IMainApp
{
public void Quit();
public void LogEventMessage(
[In, MarshalAs(UnmanagedType.BStr)] string szMessageType,
[In, MarshalAs(UnmanagedType.BStr)] string szMessage );
public bool GetNoteDetails(
[In, MarshalAs(UnmanagedType.BStr)] string szQualifiedName,
[Out, MarshalAs(UnmanagedType.BStr)] out string lpbstrOperator );
out DateTime lpdtDate );
}
}
Can someone gimme a hand to understand this? Perhaps point out a totally obvious stupid mistake that I've made? =)
Your sample C++ code is using late-binding through IDispatch. Which is the correct way to do it, your COM server doesn't support early binding. You can tell from the IDL, it uses dispinterface instead of interface.
The equivalent way in C# is by using the C#
dynamic
keyword. Like this: