编程语言
首页 > 编程语言> > C#-VARIANT(VT_PTR)的COM互操作和封送处理

C#-VARIANT(VT_PTR)的COM互操作和封送处理

作者:互联网

我们使用第三方COM对象,在某些情况下,其中一种方法将返回VT_PTR类型的VARIANT.这使默认的.NET封送拆收器不满意,它引发以下错误:

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

是否有一种优雅的方法绕过此类封送处理程序的行为并在托管端获取底层非托管指针?

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

>定制封送处理程序:

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

我确实实现了IntPtrMarshaler,只是为了发现互操作层使进程崩溃,甚至在调用任何ICustomMarshaler方法之前也是如此.也许VARIANT *参数类型与自定义封送程序不兼容.
>使用重新定义的getAttribute方法重写(或克隆)C#接口定义(如下所示),并手动对输出VARIANT进行所有封送处理:

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

这似乎不太好(接口本身还有其他30种方法).它还会破坏现有的,不相关的代码段,这些代码段已经使用了getAttribute而没有任何问题.
>从vtable获取getAttribute的非托管方法地址(使用Marshal.GetComSlotForMethodInfo等),然后对我自己的自定义委托类型进行手动调用和封送处理(使用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(与.NET一样)的COM通用封送处理器(OLEAUT)进行.

从理论上讲,在这种情况下,您可以将另一个封送程序替换为另一个(在COM级别,而不是在NET级别),但这意味着要在C端以及可能在注册表中添加一些代码(代理/存根,IMarshal等). ).

标签:com,interop,com-interop,c,net
来源: https://codeday.me/bug/20191109/2012270.html