c# - VARIANT(VT_PTR) 的 COM 互操作和编码(marshal)处理

标签 c# .net com interop com-interop

我们使用第 3 方 COM 对象,其中一种方法在特定条件下返回 VT_PTR 类型的 VARIANT。这会扰乱默认的 .NET 编码(marshal)拆收器,从而引发以下错误:

Managed Debugging Assistant 'InvalidVariant' : 'An invalid VARIANT was detected during a conversion from an unmanaged VARIANT to a managed object. Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss.

方法签名:

// (Unmanaged) IDL:
HRESULT getAttribute([in] BSTR strAttributeName, [retval, out] VARIANT* AttributeValue);

// C#:
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);

是否有一种优雅的方式来绕过这种编码(marshal)拆收器的行为并在托管端获取底层的非托管指针?

到目前为止我考虑过/尝试过的:

  • 自定义编码(marshal)拆收器:

    [return: MarshalAs(UnmanagedType.CustomMarshaler, 
    MarshalTypeRef = typeof(IntPtrMarshaler))]
    object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
    

    我确实实现了 IntPtrMarshaler,只是为了发现互操作层甚至在我的任何 ICustomMarshaler 方法被调用之前就崩溃了。也许,VARIANT* 参数类型与自定义编码(marshal)拆收器不兼容。

  • 使用重新定义的 getAttribute 方法(如下所示)重写(或克隆)C# 接口(interface)定义,并手动为输出 VARIANT 执行所有编码(marshal)处理:

    void getAttribute(
        [In, MarshalAs(UnmanagedType.BStr)],
        string strAttributeName, 
        IntPtr result);
    

    这看起来不太好(接口(interface)本身有 30 多个其他方法)。它还会破坏现有的、不相关的代码片段,这些代码片段已经毫无问题地使用了 getAttribute

  • 从 vtable(使用 Marshal.GetComSlotForMethodInfo 等)获取 getAttribute 的非托管方法地址,然后针对我自己的自定义委托(delegate)类型进行手动调用和编码(使用 Marshal.GetDelegateForFunctionPointer 等)。

    到目前为止,我已经采用了这种方法,它似乎工作正常,但对于本应简单的事情来说,感觉太过分了。

对于这种情况,我是否遗漏了一些其他可行的互操作选项?或者,也许有办法让 CustomMarshaler 在这里工作?

最佳答案

我要做的是定义一个简单的 VARIANT结构如下:

[StructLayout(LayoutKind.Sequential)]
public struct VARIANT
{
    public ushort vt;
    public ushort r0;
    public ushort r1;
    public ushort r2;
    public IntPtr ptr0;
    public IntPtr ptr1;
}

界面是这样的;

[Guid("39c16a44-d28a-4153-a2f9-08d70daa0e22"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface MyInterface
{
    VARIANT getAttributeAsVARIANT([MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}

然后,像这样在静态类中的某处添加一个扩展方法,这样调用者就可以使用 MyInterface 获得相同的编码体验:

public static object getAttribute(this MyInterface o, string strAttributeName)
{
    return VariantSanitize(o.getAttributeAsVARIANT(strAttributeName));
}

private static object VariantSanitize(VARIANT variant)
{
    const int VT_PTR = 26;
    const int VT_I8 = 20;

    if (variant.vt == VT_PTR)
    {
        variant.vt = VT_I8;
    }

    var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf<VARIANT>());
    try
    {
        Marshal.StructureToPtr(variant, ptr, false);
        return Marshal.GetObjectForNativeVariant(ptr);
    }
    finally
    {
        Marshal.FreeCoTaskMem(ptr);
    }
}

这对普通变体没有任何作用,只会为 VT_PTR 案例打补丁。

请注意,这仅在调用者和被调用者位于同一 COM 公寓中时才有效。

如果不是,您将返回 DISP_E_BADVARTYPE 错误,因为必须完成编码,默认情况下,它将由仅支持 Automation compatible data types 的 COM 通用编码器 (OLEAUT) 完成。 (就像 .NET 一样)。

在这种情况下,理论上,您可以用另一个编码(marshal)拆收器替换这个编码(marshal)拆收器(在 COM 级别,而不是在 NET 级别),但这意味着要在 C++ 端添加一些代码,并且可能在注册表中添加一些代码(代理/ stub ,IMarshal等)。

关于c# - VARIANT(VT_PTR) 的 COM 互操作和编码(marshal)处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49323410/

相关文章:

c# - 为什么 Graphics.MeasureString() 返回的数字高于预期?

c# - 如何使Entity Framework生成的类实现INotifyPropertyChanged?

c++ - MFC:类似于 Windows Explorer 的应用程序,用于与其主线程并行导出文件

c# - C#查询txt文件中的特定行

c# - 在 F# 中处理资源清理的功能方法

Delphi 错误 E1026 找不到文件 : "myprojectname.tlb"

c# - WPF RichTextBox - 插入符号位置的父元素

c# - Entity Framework 从具有子实体的父实体中提取简单列表

C# FontFamily 不显示新字体

.net - Automapper AfterMap 函数初始化类