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

C#调用非托管C++ DLL时封送结构体数组以获取测量设备记录数据的问题

C#调用非托管C++ DLL时封送结构体数组以获取测量设备记录数据的问题

嘿,我看你现在在C#里调用非托管C++ DLL控制USB测量设备,已经搞定单值测量了,却卡在获取记录数据这一步——核心是搞不定结构体数组OutBuffer的封送对吧?先把你贴的代码片段整理清楚:

int OutNo = 1; //Measurement head #1 selected  
int NumOfBuffer = 10; //Number of datapoints recorded 
LKIF_FLOATVALUE[] OutBuffer = new LKIF_FLOATVALUE[10]; //data buffer 
int NumReceived = 0; //number of values received

要解决这个问题,首先得让C#的结构体和非托管C++的结构体内存布局完全匹配,这是封送的核心。下面一步步来:

第一步:定义匹配的C#结构体

首先你得确认非托管C++里LKIF_FLOATVALUE的具体定义,假设它是类似这样的:

typedef struct {
    float value;       // 测量值
    unsigned int status; // 状态码(比如是否有效)
} LKIF_FLOATVALUE;

那对应的C#结构体要加上[StructLayout]特性,强制内存布局和C++一致:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct LKIF_FLOATVALUE
{
    public float value;
    public uint status; // 对应C++的unsigned int,类型必须严格匹配
    // 如果C++结构体还有其他字段,要一一对应添加,比如时间戳之类的
}

第二步:正确导入DLL函数

接下来是导入获取记录数据的C++函数,假设它的原型是:

int GetRecordedData(int outNo, int numOfBuffer, LKIF_FLOATVALUE* outBuffer, int* numReceived);

这里有两种靠谱的封送方式,你可以选适合自己的:

方式一:直接用数组(简单省心,适合固定大小数组)

这种方式下CLR会自动帮你处理内存封送,只需要在参数上加[Out]特性,告诉CLR这是输出参数:

[DllImport("你的DLL文件名.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetRecordedData(
    int outNo, 
    int numOfBuffer, 
    [Out] LKIF_FLOATVALUE[] outBuffer, 
    out int numReceived
);

调用的时候就用你原来的代码逻辑,直接传数组就行:

int OutNo = 1;
int NumOfBuffer = 10;
LKIF_FLOATVALUE[] OutBuffer = new LKIF_FLOATVALUE[NumOfBuffer];
int NumReceived = 0;

// 调用DLL函数
int resultCode = GetRecordedData(OutNo, NumOfBuffer, OutBuffer, out NumReceived);

// 注意:只用前NumReceived个元素,避免越界
for(int i = 0; i < NumReceived; i++)
{
    Console.WriteLine($"第{i+1}条数据:值={OutBuffer[i].value},状态={OutBuffer[i].status}");
}

方式二:用IntPtr手动封送(灵活,适合动态大小数组)

如果你的数组大小不确定,或者需要更精细控制内存,就用这种方式:

[DllImport("你的DLL文件名.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetRecordedData(
    int outNo, 
    int numOfBuffer, 
    IntPtr outBuffer, 
    out int numReceived
);

// 调用示例
int OutNo = 1;
int NumOfBuffer = 10;
int structSize = Marshal.SizeOf(typeof(LKIF_FLOATVALUE));
// 分配非托管内存
IntPtr outBufferPtr = Marshal.AllocHGlobal(structSize * NumOfBuffer);
int NumReceived = 0;

try
{
    int resultCode = GetRecordedData(OutNo, NumOfBuffer, outBufferPtr, out NumReceived);
    
    // 把非托管内存的数据复制到C#数组
    LKIF_FLOATVALUE[] OutBuffer = new LKIF_FLOATVALUE[NumReceived];
    for(int i = 0; i < NumReceived; i++)
    {
        IntPtr elementPtr = IntPtr.Add(outBufferPtr, i * structSize);
        OutBuffer[i] = (LKIF_FLOATVALUE)Marshal.PtrToStructure(elementPtr, typeof(LKIF_FLOATVALUE));
    }
    
    // 这里处理你的数据...
}
finally
{
    // 一定要释放非托管内存,避免内存泄漏!
    Marshal.FreeHGlobal(outBufferPtr);
}

几个关键注意事项

  • 调用约定要匹配:C++函数默认是Cdecl,但有些DLL会用StdCall,如果调用后出现崩溃或者返回值异常,就换一下CallingConvention参数。
  • 类型严格对应:C++的unsigned int对应C#的uint,float对应float,不能随便用int或者double替换,否则内存布局会乱。
  • 注意数组边界:NumReceived是实际返回的有效数据个数,一定要用它来遍历数组,不要直接用NumOfBuffer,不然会读到无效数据。

备注:内容来源于stack exchange,提问作者Pierre B

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 16:39:36