从 C++ 编组到 C# 时 CLR 崩溃
Posted
技术标签:
【中文标题】从 C++ 编组到 C# 时 CLR 崩溃【英文标题】:CLR Crash when Marshalling from C++ to C# 【发布时间】:2014-09-11 18:27:59 【问题描述】:我正在使用 Nikon 的 API 来控制显微镜。当我在 C# 中实现驱动程序时,API 是用 C++ 编写的。要打开与显微镜的连接,我必须使用具有以下语法的 Open 方法:
lx_result MIC_Open ( const lx_int32 iDeviceIndex, lx_uint64& uiConnectedAccessoryMask, const lx_uint32 uiErrMsgMaxSize, lx_wchar* pwszErrMsg)
如果我按如下方式编组该技术,则每当执行垃圾收集时代码都会崩溃:
[DllImport("/filepath/Ni_Mic_Driver.dll")]
protected static extern int MIC_Open ( int deviceIndex , ref ulong accessoryMask , uint errorMessageMaxSize , [MarshalAsAttribute(UnmanagedType.LPWStr)] string error);
抛出 0xc000005 异常,错误代码为 80131506,表示垃圾收集器已尝试使用无效指针处理对象。 Visual Studio 2013 产生的错误消息表明:
“此错误可能是 CLR 中的错误或用户代码的不安全或不可验证部分中的错误。 此错误的常见来源包括 COM-Interop 或 PInvoke 的用户编组错误,这可能会损坏堆栈。”
按照消息建议,我将编组调整为以下内容,这不会导致 CLR 崩溃。
[DllImport("/filepath/Ni_Mic_Driver.dll")]
protected static extern int MIC_Open ( int deviceIndex , ref ulong accessoryMask , uint errorMessageMaxSize , [MarshalAsAttribute(UnmanagedType.LPStr)] string error);
我很困惑,因为我的理解是 wchar* 表示一个指向以 null 结尾的 16 位 Unicode 字符字符串的指针,它应该映射到 UnmagedType.LPWStr。但是,代码只有在我使用 UnmanagedType.LPStr 时才有效。
以下也可以,但需要更多的工作才能提取相应的字符串。
[DllImport("/filepath/Ni_Mic_Driver.dll")]
protected static extern int MIC_Open ( int deviceIndex , ref ulong accessoryMask , uint errorMessageMaxSize , IntPtr errorPointer );
关于为什么 UnmagedType.LPWStr 在与 UnmanagedType.LPStr 或 IntPtr 一起运行时会导致崩溃的任何想法?
【问题讨论】:
MIC_Open
对指针有什么作用?它是试图释放它,还是在它返回后继续持有它?我还应该指出,将lx_uint64&
编组为指针会导致未定义的行为;不保证引用具有与指针相同的特性。
您使用的最后一个参数允许 GC 堆被本机代码破坏。它必须是容量足够大的 StringBuilder(不是 ref)。并确保在第三个参数中传递它的容量。 -1 为了安全起见。
@cdhowie:我不确定 MIC_Open 对指针的作用。他们没有在 API 文档中指定,我也无权访问底层源代码。你会建议我用什么替换 ref ulong?
@HansPassant:我理解正确吗:我应该在第四个参数中使用 StringBuilder 而不是 String?我将尝试将第四个参数替换为[MarshalAsAttribute(UnmanagedType.LPWStr)] string
和[MarshalAsAttribute(UnmanagedType.LPWStr] StringBuilder
,并在调用编组函数的地方进行必要的更改。我会报告结果。
查看我在下面发布的解决方案。感谢您的帮助!
【参考方案1】:
感谢 Hans Passant 的推荐。我更正了编组如下:
[DllImport("/filepath/Ni_Mic_Driver.dll")]
protected static extern int MIC_Open ( int deviceIndex , ref ulong accessoryMask , uint errorMessageMaxSize , [MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder errorString);
然后我使用这个方法如下(这里简化显示):
NikonDefinitions.accessory = 0;
int errorMessageCapacity = 256;
int nikonErrorMessageMaxSize = errorMessageCapacity - 1;
StringBuilder errorMessage = new StringBuilder(errorMessageCapacity);
int nikonReturn = NikonDefinitions.MIC_Open(Convert.ToInt32(1),ref NikonDefinitions.accessory,nikonErrorMessageMaxSize,errorMessage);
【讨论】:
您应该在 stringbuilder 构造函数中设置容量,以确保它实际上具有 256 个字符的容量。 @speising,如果您不提前知道字符串中实际包含多少个字符,那么是否有必要这样做。 256 是一个任意的最大尺寸,因为 API 并未指明建议的最大尺寸。 @Juderb 这是必要的,因为如果容量非常小,那么您可能无法获取完整的诊断信息(错误消息将被截断)。您需要通过设置StringBuilder
容量来预先分配足够的空间。如果您传入 256 而没有实际将 StringBuilder
上的容量设置为 257,那么非托管代码将接收到一个指向更小的缓冲区的指针,并且可能会溢出它,从而导致未定义的行为。
@speising:API 方法要求第三个参数为 uint32。正如 Hans 建议的那样,它只能是 -1 的值。
另外,@speising,如果我将 StringBuilder 容量设置为 errorMesssageMaxSize+1 且 errorMessageMaxSize=255,或者相反并将 errorMessageMaxSize 设置为 capacity-1,这真的很重要吗?除了约定,还有什么不同吗?可以理解,将 errorMessageMaxSize 设置为 256 并最终得到 257 的 StringBuilder 容量会很尴尬,但并非不正确。以上是关于从 C++ 编组到 C# 时 CLR 崩溃的主要内容,如果未能解决你的问题,请参考以下文章
Fragment 与 Parcel 一起崩溃:调用 onPause 方法时无法编组值错误