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

如何正确将C++数据结构指针从C++ DLL传递至C#?

如何正确将C数据结构指针从C DLL传递至C#?

看起来你在处理C++ DLL的指针返回时踩了几个常见的互操作坑,我来帮你一步步梳理问题并修正:

一、先修正C#结构体的定义错误

你当前的结构体字段声明里直接赋值(比如public ushort Reserved1 = 0;)不符合C#结构体语法——结构体的字段不能在声明时初始化,必须通过构造函数或者实例化后赋值。另外,默认构造函数无需手动声明(C#会隐式提供),除非你需要自定义初始化逻辑。修正后的结构体如下:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct OD
{
    public ushort Reserved1;
    public byte Reserved2;

    // 可选:自定义构造函数统一初始化逻辑
    public OD(ushort reserved1, byte reserved2)
    {
        Reserved1 = reserved1;
        Reserved2 = reserved2;
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PORT_VALUE
{
    public ushort pIndex;
    public ushort value;
    public uint ticks;

    public PORT_VALUE(ushort index, ushort val, uint tickCount)
    {
        pIndex = index;
        value = val;
        ticks = tickCount;
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DATA
{
    public uint Ticks;
    public uint SystemTime;
    public ushort DisableNotification;
    public ushort Reserved2;
    public ushort NextValue;
    public ushort Reserved3;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 500)]
    public PORT_VALUE[] PortValues;

    public OD Reserved4;

    // 初始化数组避免空引用异常
    public DATA()
    {
        Ticks = 0;
        SystemTime = 0;
        DisableNotification = 0;
        Reserved2 = 0;
        NextValue = 0;
        Reserved3 = 0;
        PortValues = new PORT_VALUE[500];
        Reserved4 = new OD();
    }
}

说明:

  • 保留了Pack = 1与C++的#pragma pack(push,1)完全匹配,保证内存布局一致
  • PortValues的MarshalAs设置正确,与C++固定大小数组PORT_VALUE PortValues[500]对齐
  • 去掉了无效的字段声明赋值,改用构造函数初始化(或依赖C#默认的零初始化行为)

二、修正DLL导入的函数声明

你的核心错误在GetDataPtr的函数声明上!C++的bool GetDataPtr(OUT PDATA *pData)接收的是指向DATA结构体指针的指针(用于输出一个指针值),但你当前的C#声明ref DATA pData是将结构体按值传递的引用,完全不匹配。

正确的声明应该用IntPtr(C#表示非托管指针的类型)来接收:

[DllImport("API.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
internal static extern bool GetDataPtr(out IntPtr dataPtr);

说明:

  • 用out IntPtr明确表示这是输出参数,符合C++的OUT语义
  • 无需unsafe关键字(除非你要直接进行指针内存操作)
  • 确保CallingConvention.Cdecl与C函数的实际调用约定一致,如果C函数是__stdcall,需改为CallingConvention.StdCall

三、正确调用函数并转换指针为C#结构体

拿到IntPtr后,用Marshal.PtrToStructure将非托管指针指向的内存转换为C#结构体:

// 调用DLL函数获取数据指针
if (GetDataPtr(out IntPtr dataPtr) && dataPtr != IntPtr.Zero)
{
    try
    {
        // 转换非托管指针为C#结构体
        DATA data = Marshal.PtrToStructure<DATA>(dataPtr);

        // 正常访问数据
        Console.WriteLine($"Ticks: {data.Ticks}, SystemTime: {data.SystemTime}");
        for (int i = 0; i < data.PortValues.Length; i++)
        {
            var portVal = data.PortValues[i];
            if (portVal.pIndex != 0 || portVal.value != 0)
            {
                Console.WriteLine($"Port {portVal.pIndex}: Value={portVal.value}, Ticks={portVal.ticks}");
            }
        }
    }
    finally
    {
        // 关键:如果C++ DLL要求释放指针内存,必须调用对应的释放函数!
        // 例如DLL提供void FreeDataPtr(PDATA pData),需导入后调用:
        // FreeDataPtr(dataPtr);
    }
}
else
{
    Console.WriteLine("从DLL获取数据指针失败");
}

四、为什么你之前的代码会出现错误值?

你用ref DATA pData调用时,C#会把结构体实例的内存地址传递给DLL,但DLL期望的是一个指针的地址(用于写入指针值)。这导致DLL错误地将指针值覆盖到你的结构体实例内存中,所以Ticks和SystemTime会被填充为指针的低32位值,出现异常数据。

五、额外注意事项

  1. 内存生命周期:确认C++ DLL返回的指针内存管理方:
    • 如果是DLL维护的全局内存,可直接读取,但要注意数据可能被DLL后续修改
    • 如果是DLL动态分配的内存(比如new DATA()),必须要求DLL提供释放函数,否则会造成内存泄漏
  2. Unsafe场景:如果需要极致性能,可开启项目“允许不安全代码”后直接操作指针:
    unsafe
    {
        if (GetDataPtr(out IntPtr dataPtr) && dataPtr != IntPtr.Zero)
        {
            DATA* pData = (DATA*)dataPtr;
            Console.WriteLine($"Ticks: {pData->Ticks}");
        }
    }
    

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:19:30