我有一个用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
- 我收到ArgumentException
消息“VariantWrappers cannot be stored in Variants.
”Marshal.AllocHGlobal
并使用Marshal.GetNativeVariantForObject()
,但我只在COM端获得int
。 tlbimp
会将相关参数编组为UnmanagedType.Struct
。我不确定如何将tlbimp
编组为IntPtr
,或者即使这会产生影响(我也尝试使用CodePlex中的增强版tlbimp2
,但它不会似乎在其配置文件中识别出我对IntPtr
的请求。
我绝不是Interop专家,所以请随意提出一些对你来说显而易见的事情。
根据@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;
...
}
答案 0 :(得分:2)
以下是如何在不重写IL的情况下使其工作。
请注意,为简单起见,我跳过了enum param,因此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);
为方便起见,最好将其包装到接口上的扩展方法中。