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? =)