1

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

不幸的是,COM 组件期望它的数组通过引用传递——有一个明确的检查,pParamArray->vt != (VT_BYREF | VT_ARRAY | VT_VARIANT)如果它没有通过检查,它会返回一个错误。

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

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

  • 我试过把它放在一个VariantWrapper- 我得到一个ArgumentException消息“ VariantWrappers cannot be stored in Variants.
  • 我试过做一个Marshal.AllocHGlobal并使用Marshal.GetNativeVariantForObject(),但我只int在 COM 端得到一个。

tlbimp默认情况下,将有问题的参数编组为UnmanagedType.Struct. 我不确定如何将tlbimp其编组为IntPtr,或者即使这会有所作为(我也尝试使用tlbimp2CodePlex 的增强功能,但它似乎无法识别我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;

    ...
}
4

1 回答 1

2

Here's how to make it work without rewriting the IL.

Please note that for simplicity I have skipped the enum param so IDL definition of the method is like this:

[
    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);
};

You can call it by late binding call like this:

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);

For convenience, it would be better to wrap this into an extension method on the interface.

于 2013-03-05T16:54:28.527 回答