You need to enable JavaScript to run this app. C#调用C++原生POS库传Host结构体触发System.AccessViolationException排查
优惠活动
大模型
产品
解决方案
定价
更多

C#调用C++原生POS库传Host结构体触发System.AccessViolationException排查

C#调用原生C++ POS API的setHost接口触发访问违规问题定位

问题背景

在C#项目中对接C++编写的POS设备原生API,绝大多数导入接口运行正常,仅setHost函数调用时抛出System.AccessViolationException。该接口是当前所有已对接接口中唯一以结构体作为入参的接口。

双方代码定义对照

C++原生侧定义

  1. setHost导出函数签名
    __declspec(dllexport) void setHost(char* id, Host* host, char* trx, char* flag);
    

    注意:第二个入参是Host类型的指针,不是结构体值

  2. Host结构体原生内存布局
    typedef struct {
       int hostConnectionType;
       char ipAddress[15 + 1];
       int tcpPort;
       int hostTransportProtocol;
       char gtId[5 + 1];
       char gprsAPN[64 + 1];
       char gprsUserName[15 + 1];
       char gprsPassword[8 + 1];
       int tlsCertificateId;
       char personalizationId[10 + 1];
       int gtIndex;
    } Host;
    

    注意:结构体字段在内存中是按定义顺序连续排列的,无自定义对齐偏移

  3. 官方C++示例调用逻辑
    Host test_host;
    test_host.hostConnectionType = 3;
    test_host.hostTransportProtocol = 2;
    test_host.gtIndex = 9;
    strcpy(test_host.ipAddress, "199.188.177.166");
    test_host.tcpPort = 54321;
    strcpy(test_host.gtId, "12345");
    test_host.tlsCertificateId = 21985;
    strcpy(test_host.personalizationId, "001");
    // 调用时传入结构体指针地址
    setHost("00000123", &test_host, "0000000001", "1000");
    

当前C#侧实现

  1. Host托管结构体定义
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Host
    {
        public int hostConnectionType;
        public int hostTransportProtocol;
        public int tcpPort;
        public int tlsCertificateId;
        public int gtIndex;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string ipAddress;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
        public string gtId;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
        public string gprsAPN;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string gprsUserName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
        public string gprsPassword;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
        public string personalizationId;
    }
    
  2. setHost委托定义
    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private delegate void cdecl__setHost([MarshalAs(UnmanagedType.LPStr)] string id, Host host, [MarshalAs(UnmanagedType.LPStr)] string trx, [MarshalAs(UnmanagedType.LPStr)] string flags);
    
  3. 测试调用代码
    Host host = new Host()
    {
        gtId = 12345,
        gtIndex = 0,
        hostConnectionType = 3,
        hostTransportProtocol = 2,
        ipAddress = "199.188.177.166",
        personalizationId = "001",
        tcpPort = 54321,
        tlsCertificateId = 21985
    };
    _setHost("00000123", host, "0000000001", "1000");
    

问题根因定位

现有实现存在两处和原生库约定不匹配的错误,直接导致内存布局错位、非法内存访问:

  1. 结构体字段顺序完全不匹配
    C#端的Host结构体把所有int字段全部前置、所有字符串数组字段后置,和C++端「int→char数组→int→char数组」的交错内存布局完全错位,非托管层读取结构体字段时拿到的地址全部是无效的。
  2. 入参传递方式不匹配
    C++端的setHost第二个参数是Host*类型指针,当前C#委托中直接按值传递Host结构体,等价于把整个结构体的内存拷贝压入栈,而非传递结构体的地址,原生函数会把传入的结构体值的内存内容当成地址去读,直接触发非法内存访问异常。
  3. 测试代码存在基础语法错误
    gtId字段在C#端定义为string类型,测试代码中赋值整数12345本身无法通过编译,和运行时错误叠加会进一步放大内存异常概率。

修正方案

  1. 按C++原生定义的完全一致的字段顺序重写C#端的Host结构体,保证内存布局和原生侧完全对齐:
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct Host
    {
        public int hostConnectionType;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string ipAddress;
        public int tcpPort;
        public int hostTransportProtocol;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 6)]
        public string gtId;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
        public string gprsAPN;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string gprsUserName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
        public string gprsPassword;
        public int tlsCertificateId;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
        public string personalizationId;
        public int gtIndex;
    }
    
  2. 修正setHost委托的第二个参数传递规则,按引用方式传递结构体,和C++端的Host*签名匹配:
    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private delegate void cdecl__setHost(
        [MarshalAs(UnmanagedType.LPStr)] string id, 
        ref Host host, 
        [MarshalAs(UnmanagedType.LPStr)] string trx, 
        [MarshalAs(UnmanagedType.LPStr)] string flags
    );
    
  3. 修正测试代码的字段赋值逻辑,把gtId = 12345调整为字符串赋值gtId = "12345",其他调用逻辑保持不变即可正常运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.04 21:15:02