从 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 崩溃的主要内容,如果未能解决你的问题,请参考以下文章

将引用类型从 C++ 编组到 C#

Fragment 与 Parcel 一起崩溃:调用 onPause 方法时无法编组值错误

当应用程序从此活动关闭时,崩溃并显示错误 - “包裹:无法编组值...”

从 C# 到 C++ 的编组

从 C# 模块调用 C++ 函数会引发随机崩溃

未选择 /clr 时编译的 Matlab 代码崩溃