【问题标题】:How to pass an array from C# to unmanaged COM by reference (VT_BYREF)如何通过引用将数组从 C# 传递到非托管 COM (VT_BYREF)
【发布时间】:2013-02-19 10:47:38
【问题描述】:

我有一个用 C++ 编写的 COM 组件,我无法更改它的源代码,其中一个方法的参数之一是 VARIANT *pParamArray。使用tlbimp,我可以为它创建一个托管存根,并从 C# 传递一个数组。

不幸的是,COM 组件期望其数组通过引用传递 - 对 pParamArray->vt != (VT_BYREF | VT_ARRAY | VT_VARIANT) 进行了显式检查,如果未通过该检查,则会返回错误。

我有用于 COM 组件的 PDB 和源代码,因此我正在同时调试 C# 和非托管代码。我可以看到我的 object[] 的 C# 数组作为 VT_ARRAY | VT_VARIANT 传递,据我所知,它本质上是 SAFEARRAY

如何明确告诉 C# 我想通过引用传递它,以便远端的类型具有 VT_BYREF 掩码?

  • 我尝试将其放入 VariantWrapper - 我收到带有消息“VariantWrappers cannot be stored in Variants.”的ArgumentException
  • 我尝试过使用Marshal.AllocHGlobal 并使用Marshal.GetNativeVariantForObject(),但我只在COM 端得到int

tlbimp 默认将相关参数编组为UnmanagedType.Struct。我不确定如何将tlbimp 编组为IntPtr,或者即使这会有所作为(我也尝试使用来自 CodePlex 的增强型tlbimp2,但它似乎无法识别我的请求IntPtr 在其配置文件中)。

我绝不是 Interop 专家,因此请随意提出一些对您来说可能显而易见的建议。

更新 1

应@ZdeslavVojkovic 的要求,以下是 IDL 的相关部分:

[
    uuid(01234567-89AB-CDEF-0123-3456789ABCDE),
    version(1.0),
    helpstring("XXX")
]
library LAbc
{
    [
        object,
        uuid(01234567-89AB-CDEF-0123-3456789ABCDE),
        dual,
        helpstring("XXX"),
        pointer_default(unique)
    ]
    interface IAbc : IDispatch
    {
            [id(1), helpstring("XXX")]
            HRESULT CallFunction([in] myEnum Function, [in, out] VARIANT* pParamArray, [out, retval] long* pVal);
    };

    [
        uuid(01234567-89AB-CDEF-0123-3456789ABCDE),
        helpstring("XXXs")
    ]
    coclass Abc
    {
        [default] interface IAbc;
    };
};

这里是方法签名本身和参数类型的内部检查:

STDMETHODIMP XAbc::CallFunction(myEnum Function, VARIANT *pParamArray, long *pVal)
{
    ...

    // we must get a pointer to an array of variants
    if(!pParamArray ||
        (pParamArray->vt != (VT_BYREF | VT_ARRAY | VT_VARIANT)) ||
        !(psa = *pParamArray->pparray))
        return E_INVALIDARG;

    ...
}

【问题讨论】:

  • 你能给出接口的确切定义和方法的签名吗?
  • @ZdeslavVojkovic 在上面的问题中添加了 IDL 的相关部分、签名以及我如何称呼它。
  • 我相信如果不重写生成的互操作 dll 中的编组信息,这是不可能的。如果您将重写以将数组声明为IntPtr 而不是ref object,那么它可以工作
  • 我放弃了TLBIMP,自己编写了互操作代码。即使我将数组声明为IntPtr 并在调用该方法之前手动将数组编组为IntPtr,它仍然VT_ARRAY | VT_VARIANT 的形式出现在远端,而不是VT_BYREF | VT_ARRAY | VT_VARIANT。在这里完全被难住了——下一步是尝试制作一个 C++ 包装器并从 C# 调用它(如下面的@jacob-seleznev 建议的那样。
  • 是的,这里与修改后的互操作 IL 相同...

标签: c# com interop marshalling com-interop


【解决方案1】:

以下是如何在不重写 IL 的情况下使其工作。

请注意,为简单起见,我跳过了枚举参数,因此方法的 IDL 定义如下:

[
    object,
    uuid(E2375DCC-8B5B-4BD3-9F6A-A9C1F8BD8300),
    dual,
    helpstring("IDummy Interface"),
    pointer_default(unique)
]
interface IDummy : IDispatch
{
    [id(1)] HRESULT Fn([in, out] VARIANT *pParamArray, [out, retval]long *pVal);
};

你可以像这样通过后期绑定调用来调用它:

INTEROPXLib.IDummy d = new INTEROPXLib.DummyClass();

object data = new object[3]; // method argument, i.e. pParamArray value

var t = typeof(INTEROPXLib.IDummy);
object[] args = new object[1]; // array which will contain all method arguments
args[0] = data; // data is the first argument, i.e. first element of args array

ParameterModifier[] pms = new ParameterModifier[1];
ParameterModifier pm = new ParameterModifier(1);
pm[0] = true; // pass the 1st argument by reference
pms[0] = pm;  // add pm to the array of modifiers 

// invoke Fn by name via IDispatch interface
var ret = t.InvokeMember("Fn", System.Reflection.BindingFlags.InvokeMethod, null, d, args, pms, null, null);
Console.Out.WriteLine("Result = " + ret);

为了方便,最好把它封装到接口上的扩展方法中。

【讨论】:

  • 很棒的东西——我已经制作了一个中间代理 COM 对象,但这似乎工作得很好而且更干净。我按照您的建议在接口上编写了一个扩展方法 - 使 API 非常干净。
猜你喜欢
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 2011-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-28
  • 1970-01-01
相关资源
最近更新 更多