我们使用第 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/