Failed to cast result of Marshal.GetActiveObject f

2019-09-08 16:09发布

问题:

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

回答1:

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:

dynamic obj = Marshal.GetActiveObject("MyLibrary.Application");
obj.Quit();