C#回调向C++调用者返回字节数组异常排查
问题分析与解决方案
C++回调的参数unsigned char(*ptrATR)[32]是指向长度为32的unsigned char数组的指针,你当前的C#委托声明未正确匹配该类型,导致内存布局不匹配,进而出现数据随机异常。
核心问题
你原委托中out byte[]配合MarshalAs(UnmanagedType.LPArray, SizeConst = 32)的写法,会被CLR marshaler处理为unsigned char*(单个字节的指针),但C期望的是unsigned char(*)[32](指向整个32字节数组的指针),两种指针的类型语义与内存布局完全不同,因此C端接收到的内存地址指向错误,数据自然出现异常。
正确实现方式
方案1:使用固定大小结构体(推荐,安全无需unsafe)
- 定义对应C++数组的结构体:
[StructLayout(LayoutKind.Sequential)] public struct ATRArray { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] Data; }
- 修改委托声明,用
ref传递结构体:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int cb_func_card_reset(ref ATRArray atr);
- 实现回调方法:
private int CardReset(ref ATRArray atr) { // 初始化并填充32字节的ATR数据 atr.Data = new byte[32]; // 写入你的ATR数据,示例如下: for (int i = 0; i < 32; i++) { atr.Data[i] = (byte)i; } return 0; // 返回C++库期望的int值 }
- 注册回调时,务必将委托实例保存为类成员变量(防止被GC回收):
private cb_func_card_reset _cardResetDelegate; // 注册回调的代码逻辑 _cardResetDelegate = CardReset; // 调用C++库的注册函数,传入委托实例 RegisterCardResetCallback(_cardResetDelegate);
方案2:使用unsafe代码直接匹配指针类型(适合熟悉内存操作的场景)
若不想使用结构体,可直接通过unsafe代码处理指向数组的指针:
- 修改委托声明:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] unsafe public delegate int cb_func_card_reset(byte(*atr)[32]);
- 实现回调方法:
unsafe private int CardReset(byte(*atr)[32]) { // 直接操作数组指针填充数据 for (int i = 0; i < 32; i++) { (*atr)[i] = (byte)i; } return 0; }
- 同样需要将委托实例保存为类成员,避免被GC回收,注册方式与方案1一致。
额外注意事项
- 确认C++库的调用约定确实为
Cdecl,若为StdCall,需修改委托的CallingConvention参数。 - 回调委托实例必须长期存活(如作为类成员),若为局部变量,注册后被GC回收会导致调用时崩溃或数据异常。
内容的提问来源于stack exchange,提问作者grunge_scum
相关产品推荐
相关产品推荐

