C# COM互操作和变型封送(VT_PTR)
我们使用第三方COM对象,其中一个方法在特定条件下返回C# COM互操作和变型封送(VT_PTR),c#,.net,com,interop,com-interop,C#,.net,Com,Interop,Com Interop,我们使用第三方COM对象,其中一个方法在特定条件下返回VT_PTR类型的变量。这会打乱默认的.NET封送拆收器,该封送拆收器会引发以下错误: 托管调试助手“InvalidVariant”:“发现了无效的变量。” 在从非托管变量转换为托管变量期间检测到 对象将无效变量传递给CLR可能会导致意外的错误 异常、损坏或数据丢失 方法签名: // (Unmanaged) IDL: HRESULT getAttribute([in] BSTR strAttributeName, [retval, out]
VT_PTR
类型的变量。这会打乱默认的.NET封送拆收器,该封送拆收器会引发以下错误:
托管调试助手“InvalidVariant”:“发现了无效的变量。”
在从非托管变量转换为托管变量期间检测到
对象将无效变量传递给CLR可能会导致意外的错误
异常、损坏或数据丢失
方法签名:
// (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
方法之前就破坏了进程。可能,变量*
参数类型与自定义封送拆收器不兼容
- 使用重新定义的
getAttribute
方法重写(或克隆)C#接口定义(如下所示),并手动执行输出变量的所有封送处理:
void getAttribute(
[In, MarshalAs(UnmanagedType.BStr)],
string strAttributeName,
IntPtr result);
这看起来不太好(接口本身有30多个其他方法)。它还将打破现有的、不相关的代码片段,这些代码片段已经毫无问题地使用了getAttribute
- 从vtable(使用
Marshal.GetComSlotForMethodInfo
etc)获取getAttribute
的非托管方法地址,然后根据我自己的自定义委托类型执行手动调用和封送(使用Marshal.GetDelegateForFunctionPointer
etc)
到目前为止,我已经采取了这种方法,它似乎工作得很好,但对于一件本应简单的事情来说,它感觉太过份了
我是否错过了该场景中其他可行的互操作选项?或者,也许有一种方法可以让CustomMarshaler
在这里工作?我要做的是定义一个简单的结构,如下所示:
[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);
}
}
publicstaticobjectgetattribute(这个myo接口,字符串strAttributeName)
{
返回变量初始化(o.getAttributeAsVARIANT(strAttributeName));
}
私有静态对象变量初始化(变量变量)
{
常数int VT_PTR=26;
常数int VT_I8=20;
如果(variant.vt==vt_PTR)
{
variant.vt=vt_I8;
}
var ptr=Marshal.alloctaskmem(Marshal.SizeOf());
尝试
{
Marshal.StructureToPtr(变量,ptr,false);
返回Marshal.GetObjectForNativeVariant(ptr);
}
最后
{
FreeCoTaskMem元帅(ptr);
}
}
这对正常的变体没有任何作用,但只会对VT_PTR情况进行修补
注意:这仅在调用者和被调用者位于同一个COM分区时有效
如果没有,您将返回DISP_E_BADVARTYPE错误,因为封送必须完成,默认情况下,它将由COM通用封送器(OLEAUT)完成,该封送器仅支持(与.NET类似)
在理论上,你可以用另一个替换这个排序器(在COM级别,而不是在网络级别),但是这意味着在C++方面添加一些代码,可能在注册表中添加(代理/存根,IMarshal等)。< /P> < P>我自己的未来参考,这里是我如何使用问题的第三个选项:
[ComImport, Guid("75A67021-058A-4E2A-8686-52181AAF600A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInterface
{
[return: MarshalAs(UnmanagedType.Struct)]
object getAttribute([In, MarshalAs(UnmanagedType.BStr)] string strAttributeName);
}
private delegate int IInterface_getAttribute(
IntPtr pInterface,
[MarshalAs(UnmanagedType.BStr)] string name,
IntPtr result);
public static object getAttribute(this IInterface obj, string name)
{
var ifaceType = typeof(IInterface);
var ifaceMethodInfo = ((Func<string, object>)obj.getAttribute).Method;
var slot = Marshal.GetComSlotForMethodInfo(ifaceMethodInfo);
var ifacePtr = Marshal.GetComInterfaceForObject(obj, ifaceType);
try
{
var vtablePtr = Marshal.ReadIntPtr(ifacePtr);
var methodPtr = Marshal.ReadIntPtr(vtablePtr, IntPtr.Size * slot);
var methodWrapper = Marshal.GetDelegateForFunctionPointer<IInterface_getAttribute>(methodPtr);
var resultVar = new VariantClass();
var resultHandle = GCHandle.Alloc(resultVar, GCHandleType.Pinned);
try
{
var pResultVar = resultHandle.AddrOfPinnedObject();
VariantInit(pResultVar);
var hr = methodWrapper(ifacePtr, name, pResultVar);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
if (resultVar.vt == VT_PTR)
{
return resultVar.ptr;
}
try
{
return Marshal.GetObjectForNativeVariant(pResultVar);
}
finally
{
VariantClear(pResultVar);
}
}
finally
{
resultHandle.Free();
}
}
finally
{
Marshal.Release(ifacePtr);
}
}
[ComImport,Guid(“75A67021-058A-4E2A-8686-52181AAF600A”),接口类型(ComInterfaceType.InterfaceSiunknown)]
公共接口接口
{
[返回:MarshalAs(UnmanagedType.Struct)]
对象getAttribute([In,Marshallas(UnmanagedType.BStr)]字符串strAttributeName);
}
私有委托int IIINTERFACE_getAttribute(
IntPtr pInterface,
[Marshallas(UnmanagedType.BStr)]字符串名称,
IntPtr结果);
公共静态对象getAttribute(此界面对象,字符串名称)
{
var ifaceType=类型(界面);
var ifaceMethodInfo=((Func)obj.getAttribute).Method;
var slot=Marshal.GetComSlotForMethodInfo(ifaceMethodInfo);
var ifacePtr=Marshal.GetComInterfaceForObject(obj,ifaceType);
尝试
{
var vtablePtr=Marshal.ReadIntPtr(ifacePtr);
var methodPtr=Marshal.ReadIntPtr(vtablePtr,IntPtr.Size*slot);
var methodWrapper=Marshal.GetDelegateForFunctionPointer(methodPtr);
var resultVar=新的VariantClass();
var resultHandle=GCHandle.Alloc(resultVar,GCHandleType.pinted);
尝试
{
var presltvar=resultHandle.addrofPindedObject();
方差(preltvar);
var hr=methodWrapper(ifacePtr、name、preltvar);
如果(hr<0)
{
元帅。通过hr(hr)的例外情况;
}
如果(resultVar.vt==vt_PTR)
{
返回resultVar.ptr;
}
尝试
{
返回Marshal.GetObjectForNativeVariant(PRESLTvar);
}
最后
{
方差线性(PRELTVAR);
}
}
最后
{
resultHandle.Free();
}
}
最后
{
元帅放行(ifacePtr);
}
}
您是否尝试返回IntPtr?这里还有一些信息:它是一个双/IDispatch接口吗?因为它是一个双接口(它们严格遵守更严格的规则),IMHO 1)不起作用,我不知道你要怎么做2),所以如果3)工作得很好,为什么不呢(你可以给接口添加扩展方法,这是一个添加语法糖以使用它们的好方法)。我不认为这是一件“简单的事情”,因为第三方违反了变体/自动化规则。我确实试过:-)但我可以测试您的确切环境,事实上有一个重要的问题:您正在运行吗