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

C#调用GlobalPlatform接口封送错误Type could not be marshaled排查

核心问题定位

所有报错都来自GP211_SECURITY_INFO结构体和定长数组参数的P/Invoke封送配置错误,存在三个硬伤:

  1. 将本该是值类型的互操作结构体定义为class(引用类型),搭配ref传参时实际传递的是二级指针,和C函数要求的结构体一级指针对应的内存布局完全不匹配
  2. Windows平台下C/C++的LONG、DWORD类型均为4字节长度,错误映射为C#的long类型(8字节),直接导致结构体后续所有字段的内存偏移全部错位
  3. 未显式指定结构体字节对齐Pack值、定长数组的元素封送类型,且函数签名中四个32字节定长入参未加定长数组封送特性,封送器默认按动态数组指针处理,和原生端栈上分配的定长数组内存布局不匹配
修复步骤
  • 将GP211_SECURITY_INFO从class改为struct值类型,显式指定LayoutKind.Sequential布局和Pack=1(GlobalPlatform原生库默认使用1字节对齐,若C端头文件显式指定了其他pack值则对应修改)
  • 修正值类型映射:C端LONG、DWORD统一映射为C#的int(4字节),禁止使用8字节的long类型
  • 所有定长字节数组的ByValArray封送特性,必须显式添加ArraySubType = UnmanagedType.U1,指定数组元素为1字节无符号类型,避免封送器做默认类型转换
  • 函数签名中baseKey、S_ENC、S_MAC、DEK四个32字节定长参数,添加[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]特性,明确告知封送器这是栈上定长数组而非动态数组指针
  • 检查项目目标平台和原生DLL的架构匹配:大部分GP操作原生库为32位编译,建议优先将C#项目目标平台设为x86,避免32/64位混跑导致的内存偏移错误
正确代码示例

GP211_SECURITY_INFO结构体定义

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct GP211_SECURITY_INFO
{
    public byte securityLevel;
    public byte secureChannelProtocol;
    public byte secureChannelProtocolImpl;
    
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U1)]
    public byte[] C_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U1)]
    public byte[] R_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U1)]
    public byte[] encryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32, ArraySubType = UnmanagedType.U1)]
    public byte[] dataEncryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16, ArraySubType = UnmanagedType.U1)]
    public byte[] lastC_MAC;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8, ArraySubType = UnmanagedType.U1)]
    public byte[] lastR_MAC;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16, ArraySubType = UnmanagedType.U1)]
    public byte[] invokingAid;
    
    public byte keySetVersion;
    public byte keyIndex;
    
    public int invokingAidLength;
    public int sessionEncryptionCounter;
    public int keyLength;
}

修正后的DllImport签名

[DllImport("globalPlatform.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GP211_mutual_authentication_wrapper(
    OPGP_CARD_CONTEXT cardContext, 
    OPGP_CARD_INFO cardInfo, 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] byte[] baseKey, 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] byte[] S_ENC, 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] byte[] S_MAC, 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] byte[] DEK, 
    int keyLength, 
    byte keySetVersion, 
    byte keyIndex, 
    byte secureChannelProcotol, 
    byte secureChannelProtocolImpl, 
    byte securityLevel, 
    byte derivationMethod, 
    ref GP211_SECURITY_INFO secInfo, 
    ref OPGP_ERROR_STATUS cardStatus);
调用验证注意事项
  • 实例化GP211_SECURITY_INFO时,必须将所有定长字节数组初始化为声明长度,禁止留null;C#中new byte[长度]默认即为全0值,封送后C端可正确读取
  • 若修改后仍有异常,分别在C#端调用Marshal.SizeOf<GP211_SECURITY_INFO>()、C端调用sizeof(GP211_SECURITY_INFO)打印结构体大小,两个值必须完全相等;若存在差值,逐字段核对顺序、类型长度、Pack值即可定位问题

之前遇到的函数返回时抛出unsigned longC++异常,本质是结构体封送大小不匹配,原生函数写入返回值时越界踩踏了栈上的SEH异常链或返回地址,只要结构体内存布局、大小完全和原生端对齐,该错误会直接消失。之前初始化全0数组但C端读到随机值,也是字段偏移错位导致C端读取的内存位置和实际赋值位置不匹配导致的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 17:57:19