【问题标题】:Marshalling arrays of VARIANT using P/Invoke使用 P/Invoke 编组 VARIANT 数组
【发布时间】:2013-09-10 18:35:46
【问题描述】:

情况: 我有一个使用 P/Invoke 的非托管 (C++) DLL 的托管(C#、.NET 2.0)应用程序。除了“简单”方法(POD 参数/返回值)之外,还需要将 boost::variant 值数组传递给代码。原因是这些方法传递报表数据(类似于 Excel 单元格,可以是任何类型)。 C# 代码接受它们作为装箱的“对象”。

之前的实现要求使用 COM VARIANT 的 SafeArray。但是,由于编码不佳/未对其进行测试,编组结果是内存泄漏。现在我必须找到另一个编组数据的选项。

之前的实现是这样的: C++:

extern "C" __declspec(dllexport) void GetReport(VARIANT& output) {
    // ... here a SafeArray of VARIANT values was created
    output.vt = VT_VARIANT | VT_ARRAY;
    output.parray = safeArray;
}

C#

[DllImport("CppLibrary.dll")]
private static extern void GetReport(out object output);

//....
object data;
GetReport(data);
object rows = data as object[];

此特定实现通过不释放互操作结构来泄漏内存。

我尝试通过包含 SafeArray 编组指令来更改原型:

C++

extern "C" __declspec(dllexport) void GetReport(SAFEARRAY output) { // Also tried SAFEARRAY*, SAFEARRAY&, VARIANT, VARIANT&, VARIANT*
    // Internal stuff
}

C#

private static extern void GetReport([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]ref object[] output);

但是,我唯一实现的结果要么是空的结果对象,要么是由于内存损坏/堆栈溢出而崩溃。

问题: 如何正确地将此类数据类型(VARIANT 类型结构的数组)编组为 C#? 我可以使 C++ DLL 成为 COM 的,但这需要重写相当多的代码。有没有更简单的方法摆脱这种情况?也许我错过了什么。

【问题讨论】:

    标签: c# c++ interop pinvoke variant


    【解决方案1】:

    有这个例子:http://limbioliong.wordpress.com/2011/03/20/c-interop-how-to-return-a-variant-from-an-unmanaged-function/

    最后他们直接使用IntPtr(他们将其用作返回值,您必须将其用作out IntPtr),然后是C#中的Marshal.GetObjectForNativeVariant()VariantClear()Marshal.FreeCoTaskMem()侧,而在 C/C++ 侧,VARIANT 分配有CoTaskMemAlloc()

    [DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
    static extern void MyFunction(out IntPtr ptr);
    
    [DllImport("oleaut32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    static extern Int32 VariantClear(IntPtr pvarg);
    
    IntPtr pVariant;
    MyFunction(out pVariant);
    
    object objRet = Marshal.GetObjectForNativeVariant(pVariant);
    
    VariantClear(pVariant);
    Marshal.FreeCoTaskMem(pVariant);
    
    pVariant = IntPtr.Zero;
    

    显然你可以在你的 dll 中公开另一个 C 函数来释放 VARIANT(在你的库中公开 Free 方法总是正确的,这样调用者就可以使用它们而不是问自己“我应该如何释放这个内存?”)

    【讨论】:

    • 嗯。这似乎是一个可行的选择,我会尝试的。你知道它是否适用于嵌套变体(vt = VT_VARIANT | VT_ARRAY,如上例所示)?
    • @DarkWanderer VariantClear Safearrays of variant will also have VariantClear called on each member. Using VariantClear in these cases ensures that code will continue to work if Automation adds new variant types in the future.
    • 哇哦!这完全可以理解。我仍然需要稍微重写接口,但这与切换到 COM 相比不算什么。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2012-01-21
    • 2017-01-18
    • 2014-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多