C#调用C++原生POS库传Host结构体触发System.AccessViolationException排查
C#调用原生C++ POS API的setHost接口触发访问违规问题定位
问题背景
在C#项目中对接C++编写的POS设备原生API,绝大多数导入接口运行正常,仅setHost函数调用时抛出System.AccessViolationException。该接口是当前所有已对接接口中唯一以结构体作为入参的接口。
双方代码定义对照
C++原生侧定义
setHost导出函数签名__declspec(dllexport) void setHost(char* id, Host* host, char* trx, char* flag);注意:第二个入参是
Host类型的指针,不是结构体值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;注意:结构体字段在内存中是按定义顺序连续排列的,无自定义对齐偏移
- 官方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#侧实现
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; }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);- 测试调用代码
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");
问题根因定位
现有实现存在两处和原生库约定不匹配的错误,直接导致内存布局错位、非法内存访问:
- 结构体字段顺序完全不匹配
C#端的Host结构体把所有int字段全部前置、所有字符串数组字段后置,和C++端「int→char数组→int→char数组」的交错内存布局完全错位,非托管层读取结构体字段时拿到的地址全部是无效的。 - 入参传递方式不匹配
C++端的setHost第二个参数是Host*类型指针,当前C#委托中直接按值传递Host结构体,等价于把整个结构体的内存拷贝压入栈,而非传递结构体的地址,原生函数会把传入的结构体值的内存内容当成地址去读,直接触发非法内存访问异常。 - 测试代码存在基础语法错误
gtId字段在C#端定义为string类型,测试代码中赋值整数12345本身无法通过编译,和运行时错误叠加会进一步放大内存异常概率。
修正方案
- 按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; } - 修正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 ); - 修正测试代码的字段赋值逻辑,把
gtId = 12345调整为字符串赋值gtId = "12345",其他调用逻辑保持不变即可正常运行。
内容的提问来源于stack exchange,提问作者Andrea Fausti
相关产品推荐
相关产品推荐

