通过COM将一个对象从C++传递给C#

4

你好,我有一个在C#中可见的COM API,如下所示:

public void DoSomething(string par1, string par2, object additionalParameter)

这个想法是,根据字符串参数的值,我期望第三个参数是不同的类,并在实现中适当地进行转换(我知道这种设计并不理想,但我在这里没有太多的灵活性)。

假设对于某些字符串参数的组合,附加参数的类型如下:

[ComVisible(true)]
[Serializable]
public class TestObject    
{
    public string String{ get; set; }

    public long Long { get; set; }
}

我需要从一些非托管代码中调用我的API方法;然而我在创建第三个参数所需的正确的variant时遇到了困难。

我正在使用CComVariant(...),传递一个指向我刚刚构建的TestObject的IDispatch

假设pTestObject是指向我的TestObject的IDispatch指针,我有以下代码:

CComVariant pObjectVariant(pTestObject);
DoSomething(BSTR(L"first"), BSTR(L"second"), pObjectVariant);

然而,当C#函数最终被调用时,我发现对象的类型是bool而不是我期望的TestObject

有什么想法吗?

Stefano


当您在调用之前使用非托管调试器查看pTestObject.vt时,其值是多少? - Hans Passant
实际上它确实是VT_BOOL类型。不确定为什么。也许这是默认值? - Stefano Ricciardi
1个回答

5
我有几个建议。首先,为您在COM中使用的任何东西创建一个接口,即使它只是一个普通的DTO,没有方法,只有属性。COM喜欢接口。它非常喜欢接口,以至于您在COM中接触到的所有内容都是接口。
另一个建议是,在您在COM中接触到的任何东西上放置GuidAttribute。这将确保在使用COM注册托管程序集时,您的注册表不会出现问题。COM比接口更喜欢GUID,并且如果一个接口注册到多个GUID上,COM很容易感到困惑,如果您不在代码中硬编码接口GUID,则可能会发生这种情况。具体类也需要一个GUIDAttribute。
我知道这很糟糕,但这就是为什么微软正在努力让人们远离使用COM。
话虽如此,您可能希望像这样使用C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{

    [ComVisible(true)]
    [Guid("1CEB24F2-BF13-417F-B5BC-DB8E82A56EAE")]
    public interface ITestEntity1 //This is how TestEntity1 is visible to the COM code...so it needs a Guid.
    {
        bool Thing { get; set; }
    }


    [ComVisible(true)]
    [Guid("C8B5A7C2-F67C-4271-A762-3642754F2233")]
    public class TestEntity1 : ITestEntity1  //Created by the COM runtime...needs a Guid.
    {
        public bool Thing { get; set; }
    }

    [ComVisible(true)]
    [Guid("8904A7EC-D865-4533-91EC-1F68524651D0")]
    public interface ITestEntity2
    {
        string Description { get; set; }
    }

    [ComVisible(true)]
    [Guid("668EE2E8-5A60-468B-8689-D9327090AA44")]
    public class TestEntity2 : ITestEntity2
    {
        public string Description { get; set; }
    }

    [ComVisible(true)]
    [Guid("2791082F-F505-49C4-8952-80C174E4FE96")]
    public interface ITestGateway
    {
        //MarshalAsAttribute is somewhat important, it tells the tlbexp.exe tool to mark
        // the comInputValue parameter as IUnknown* in the COM interface.
        //This is good because VARIANTS kinda suck...You'll see what I mean in the C++
        // side.  It also keeps some jack-wagon from passing a VARIANT_BOOL in
        // on your object parameter.
        void DoSomething(string a, [MarshalAs(UnmanagedType.Interface)]object comInputValue);
    }

    [ComVisible(true)]
    [Guid("C3D079F3-7869-4B3E-A742-263775C6EA63")]
    public class TestGateway : ITestGateway
    {
        public void DoSomething(string a, object comInputValue)
        {
            if (a == "yes")
            {
                var entity = (TestEntity1)comInputValue;
            }
            else
            {
                var entity = (TestEntity2) comInputValue;
            }
            //OR

            if(comInputValue is TestEntity1)
            {
                //Do whatever here, and you don't need to test
                // a string input value.
            }
            else if(comInputValue is TestEntity2)
            {
                //Other stuff is done here.
            }
            else
            {
                //Error condition??
            }
        }
    }
}

以下的C++代码可以实现该功能:

以下的C++代码可以实现该功能:

// ComClient.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#import <mscorlib.tlb> raw_interfaces_only

//This creates the CLSID_ and IID_ constants, and
// some strongly-typed interfaces.
#import "..\Debug\ClassLibrary1.tlb" no_namespace named_guids


int _tmain(int argc, _TCHAR* argv[])
{
    ITestGateway* test = NULL;

    char buffer[50];
    gets(buffer);  //Just a pause to attach the debugger in Managed + Native mode...hit enter in the console.

    CoInitialize(NULL);

    HRESULT hr = CoCreateInstance(CLSID_TestGateway,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_ITestGateway,
        reinterpret_cast<void**>(&test));

    if(FAILED(hr)) {
        printf("Couldn't create the instance!... 0x%x\n", hr);
        gets(buffer);
    } else {
        _bstr_t someString("yes");

        //Instead of fooling with CComVariant,
        // just directly create a TestEntity1...which COM will return
        // as an ITestEntity1.
        ITestEntity1* testEntity1 = NULL;
        HRESULT hr = CoCreateInstance(CLSID_TestEntity1,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_ITestEntity1,
            reinterpret_cast<void**>(&testEntity1));

        if(FAILED(hr)) {
            printf("Entity was not created!... 0x%x\n", hr);
            gets(buffer);
            return 0;
        }

        //Set some kind of property just for show.
        testEntity1->PutThing(VARIANT_FALSE);



        //If you attached your debugger with Managed code & Native code,
        // you should be able to hit a C# break point during this call.
        //Also, notice that there is no cast for testEntity1.  All interfaces
        // in COM derive from IUnknown, so you can just pass it.
        //IDispatch also derives from IUnknown, so if that's what you already have,
        // you can just pass it as well, with no cast.
        test->DoSomething(someString, testEntity1);

        printf("Something was done.");

        testEntity1->Release(); //Release anything you make through CoCreateInstance()
    }

    test->Release();

    return 0;
}

非常感谢,这解决了问题!注意:我实际上有一个TestObject的接口,只是没有在我的问题中提到,以免使问题过于混乱。也许为了更清晰,我应该把它放进去。 - Stefano Ricciardi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接