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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:48:52