简体   繁体   English

通过COM将对象从C ++传递到C#

[英]Passing an object from C++ to C# though COM

hi have a COM visible API in C# which looks like the following: 您好在C#中有一个COM可见API,如下所示:

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

The idea is that based on the value of the string parameters I expect a different class as the third parameter and cast it appropriately in the implementation (I know this design is not optimal but I don't have much flexibility here). 我的想法是,基于字符串参数的值,我希望将不同的类作为第三个参数,并在实现中适当地转换它(我知道这个设计不是最优的,但我在这里没有太大的灵活性)。

Suppose that for some combination of the string parameters the type of the additional parameter is the following: 假设对于字符串参数的某些组合,附加参数的类型如下:

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

    public long Long { get; set; }
}

I need to my API method from some unmanaged code; 我需要从一些非托管代码中获取我的API方法; however I am having difficulties in creating the a proper variant needed for the third parameter. 但是我在创建第三个参数所需的适当variant遇到了困难。

I am using the CComVariant(...) passing an IDispatch pointing to a TestObject that I have just built. 我正在使用CComVariant(...)传递指向我刚刚构建的TestObject的IDispatch

Suppose that pTestObject is an IDispatch pointer to my TestObject, I have something like the following: 假设pTestObject是指向我的TestObject的IDispatch指针,我有以下内容:

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

However, when the C# function is finally invoked, I see that the object has type bool instead of TestObject that I was expecting. 但是,当最终调用C#函数时,我看到该对象具有类型bool而不是我期望的TestObject

Any idea? 任何的想法?

Stefano 斯特凡诺

I have a couple of suggestions. 我有几个建议。 First of all, create an interface for anything that you touch in COM, even if its just a bog-standard DTO that has no methods and only properties. 首先,为您在COM中触摸的任何内容创建一个界面,即使它只是一个没有方法且只有属性的沼泽标准DTO。 COM loves interfaces. COM喜欢界面。 It loves them so much, that everything you touch in COM is an interface. 它非常爱它们,你在COM中触摸的一切都是一个界面。

The other suggestion is that you place a GuidAttribute on anything that you touch in COM. 另一个建议是你在你接触COM的任何东西上放置一个GuidAttribute。 This will ensure that your registry doesn't get crapped up when you register the managed assembly with COM. 这将确保在使用COM注册托管程序集时,您的注册表不会被删除。 COM loves GUIDs more than it loves interfaces, and it gets confused easily if an interface is registered to more than one GUID, which can happen if you don't hard-fix the interface GUIDs in code. COM喜欢GUID,而不是喜欢接口,如果接口注册到多个GUID,它会很容易混淆,如果你没有在代码中修改接口GUID,就会发生这种情况。 Concrete classes also need a GUIDAttribute. 具体类还需要GUIDAttribute。

I know it sucks, but that's why MS is trying so hard to get people away from using COM. 我知道这很糟糕,但这就是为什么MS努力让人们远离使用COM。

That being said, you probably want C# like this: 话虽这么说,你可能想要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??
            }
        }
    }
}

That can be called by the following 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM