C#调用C DLL的CheckUser函数参数接收异常排查
C# 调用C动态链接库参数错误修正
核心结论
不需要将所有函数入参手动转换为IntPtr传递,只要修正结构体封送规则、传参方式即可让DLL正确接收参数。你当前代码存在以下几类错误:
具体错误点
- 定长字符串封送规则错误:C端结构体中
char usr[127]这类定义是内联在结构体内存中的127字节定长ANSI字符数组,你直接用string类型声明字段时,默认封送为Unicode字符串指针,既不匹配字符集,也不符合内存布局,会导致Marshal.SizeOf计算出的结构体大小和C端完全不一致,DLL读取到的都是错误值。 - 传参方式不匹配:C函数签名要求传入结构体指针(
const struct CredData*),但你的DllImport声明直接按值传递结构体,相当于把结构体拷贝到栈上直接传值,C端按指针去读内存时自然拿不到正确数据。 - 字段名不匹配:你定义的
CredData、EndpointData结构体中指针字段名为credVal、endpointVal,但调用赋值时写的是credentialValue、endpointValue,属于笔误导致的字段赋值错误,无兼容处理的情况下这部分代码甚至无法编译通过。 - 未指定字符集与调用约定:默认P/Invoke采用Unicode字符集,和C端
char类型对应的ANSI字符集不匹配,即使字符串地址正确也会读出乱码;未显式指定调用约定时,若C端默认用Cdecl规则编译,会出现栈不平衡导致的运行时异常。
修正后的代码示例
首先引入封送所需命名空间:
using System.Runtime.InteropServices;
修正结构体声明,为定长字符串添加封送特性,明确内存布局与字符集:
// 显式指定顺序布局、ANSI字符集,匹配C端内存规则 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct CredPair { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)] public string usr; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)] public string pas; } public enum CredType { PAIR, KEY } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct EndpointDirect { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 127)] public string url; } public enum EndpointType { DIRECT } [StructLayout(LayoutKind.Sequential)] public struct CredData { public CredType credType; public IntPtr credVal; } [StructLayout(LayoutKind.Sequential)] public struct EndpointData { public EndpointType endpointType; public IntPtr endpointVal; } // 按DLL实际定义调整MyErrCode的枚举值即可 public enum MyErrCode { Success = 0, InvalidParam = 1 }
修正DllImport声明,用ref关键字传递结构体对应C端指针参数,显式指定字符集与调用约定:
[DllImport("mydll.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] public static extern MyErrCode CheckUser(ref CredData cred_data, ref EndpointData endpoint_data);
修正调用逻辑,注意手动分配的非托管内存在调用完成后必须释放,避免内存泄漏:
// 初始化认证参数 CredData objCredData = new CredData(); objCredData.credType = CredType.PAIR; CredPair objPair = new CredPair(); objPair.usr = "abc@xyz.com"; objPair.pas = "admin@1234"; // 分配非托管内存存放CredPair结构体 IntPtr pCredPair = Marshal.AllocHGlobal(Marshal.SizeOf(objPair)); Marshal.StructureToPtr(objPair, pCredPair, false); objCredData.credVal = pCredPair; // 字段名与结构体定义保持一致 // 初始化端点参数 EndpointData objEndpoint = new EndpointData(); objEndpoint.endpointType = EndpointType.DIRECT; EndpointDirect epd = new EndpointDirect(); epd.url = "example.com"; IntPtr pEndpointDirect = Marshal.AllocHGlobal(Marshal.SizeOf(epd)); Marshal.StructureToPtr(epd, pEndpointDirect, false); objEndpoint.endpointVal = pEndpointDirect; // 字段名与结构体定义保持一致 // 加ref关键字传参,对应C端的指针类型 MyErrCode error_code = CheckUser(ref objCredData, ref objEndpoint); // 调用完成后释放非托管内存 Marshal.FreeHGlobal(pCredPair); Marshal.FreeHGlobal(pEndpointDirect);
补充说明
如果不想手动分配内层结构体的非托管内存,也可以用显式内存偏移(FieldOffset)声明联合体类型直接传参,但对于这种带类型标签+void*的可变结构体,手动通过IntPtr分配内存的方式兼容性最好,不容易出现内存对齐错误。
内容的提问来源于stack exchange,提问作者The King
相关产品推荐
相关产品推荐

