You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

  1. 定义对应C++数组的结构体:
[StructLayout(LayoutKind.Sequential)]
public struct ATRArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
    public byte[] Data;
}
  1. 修改委托声明,用ref传递结构体:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int cb_func_card_reset(ref ATRArray atr);
  1. 实现回调方法:
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值
}
  1. 注册回调时,务必将委托实例保存为类成员变量(防止被GC回收):
private cb_func_card_reset _cardResetDelegate;

// 注册回调的代码逻辑
_cardResetDelegate = CardReset;
// 调用C++库的注册函数,传入委托实例
RegisterCardResetCallback(_cardResetDelegate);

方案2:使用unsafe代码直接匹配指针类型(适合熟悉内存操作的场景)

若不想使用结构体,可直接通过unsafe代码处理指向数组的指针:

  1. 修改委托声明:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate int cb_func_card_reset(byte(*atr)[32]);
  1. 实现回调方法:
unsafe private int CardReset(byte(*atr)[32])
{
    // 直接操作数组指针填充数据
    for (int i = 0; i < 32; i++)
    {
        (*atr)[i] = (byte)i;
    }
    return 0;
}
  1. 同样需要将委托实例保存为类成员,避免被GC回收,注册方式与方案1一致。

额外注意事项

  • 确认C++库的调用约定确实为Cdecl,若为StdCall,需修改委托的CallingConvention参数。
  • 回调委托实例必须长期存活(如作为类成员),若为局部变量,注册后被GC回收会导致调用时崩溃或数据异常。

内容的提问来源于stack exchange,提问作者grunge_scum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:05:28