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

C#调用C++ DLL传递结构体数组触发内存访问异常求助

解决C#调用C++ DLL传递结构体数组引发的System.AccessViolationException问题

首先直接回答你的问题:不是必须要将Pos[]转换为IntPtr,但当前代码存在几个关键问题导致内存访问违规,先从修复结构体映射和初始化入手,若仍有问题再考虑手动管理非托管内存(用IntPtr)。

核心问题分析

出现System.AccessViolationException的主要原因是托管/非托管结构体的内存布局不匹配,以及结构体成员未正确初始化,具体点如下:

  1. C#结构体成员默认是私有(private),但CLR在封送处理时无法访问私有成员,导致内存映射错误。
  2. 内存对齐(Pack值)不匹配:C默认的内存对齐通常是8字节(VC),但你在C#里强制设置了Pack=1,导致结构体总大小和C++端不一致。
  3. C#数组中的Pos实例的pFacialData数组未初始化,封送时会出现空引用或内存长度不足的问题。
  4. 若C++的Detect函数需要知道数组长度,但当前参数未传递,可能导致函数越界访问内存。

可行解决方案

方案一:修复结构体映射与数组初始化

这是最直接的修复方式,无需改用IntPtr:

1. 修正C#结构体的访问修饰符与对齐设置

将所有结构体成员改为public,并对齐C的内存布局(去掉Pack=1,保持默认对齐,若C显式设置了#pragma pack(n),则C#同步设置Pack=n):

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Pos {
    public RECT rcFace;
    public POINT ptNose;
    public FaceAngle fAngle;
    public int nQuality;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 512)]
    public byte[] pFacialData;
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT {
    public int left;
    public int top;
    public int right;
    public int bottom;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT {
    public int x;
    public int y;
}

[StructLayout(LayoutKind.Sequential)]
public struct FaceAngle {
    public int yaw;
    public int pitch;
    public int roll;
    public float confidence;
}

2. 正确初始化数组中的每个Pos实例

创建数组后,手动初始化每个元素的pFacialData数组以及其他成员(和C++结构体的构造逻辑保持一致):

[DllImport(THFaceDLLName, EntryPoint = "Detect")]
public static extern int Detect([In, Out] Pos[] posArray);

void detect() {
    Pos[] facePosArray = new Pos[10];
    // 初始化每个Pos实例
    for (int i = 0; i < facePosArray.Length; i++) {
        facePosArray[i].pFacialData = new byte[512];
        facePosArray[i].nQuality = 0;
        // 初始化其他值类型成员为默认值
        facePosArray[i].fAngle = new FaceAngle();
        facePosArray[i].ptNose = new POINT();
        facePosArray[i].rcFace = new RECT();
    }
    int faceNum = Detect(facePosArray);
}

3. 验证结构体大小一致性

在C++中用sizeof(Pos)计算结构体大小,在C#中用Marshal.SizeOf(typeof(Pos))对比,若两者相等,说明内存布局匹配;若不等,调整Pack值直到一致。


方案二:手动用IntPtr管理非托管内存(更可控)

若方案一仍无法解决问题,可手动分配非托管内存,将结构体数组拷贝到非托管内存中调用函数,再将结果拷贝回托管内存:

1. 修改DllImport定义

将参数改为IntPtr:

[DllImport(THFaceDLLName, EntryPoint = "Detect")]
public static extern int Detect(IntPtr posPtr);

2. 手动管理内存与调用

void detect() {
    int arrayLength = 10;
    int structSize = Marshal.SizeOf(typeof(Pos));
    // 分配非托管内存
    IntPtr posPtr = Marshal.AllocHGlobal(arrayLength * structSize);

    try {
        // 将每个初始化好的Pos拷贝到非托管内存
        for (int i = 0; i < arrayLength; i++) {
            Pos pos = new Pos();
            pos.pFacialData = new byte[512];
            pos.nQuality = 0;
            // 计算当前结构体在非托管内存中的偏移位置
            IntPtr currentPtr = IntPtr.Add(posPtr, i * structSize);
            Marshal.StructureToPtr(pos, currentPtr, false);
        }

        // 调用Detect函数
        int faceNum = Detect(posPtr);

        // 将结果从非托管内存拷贝回托管数组
        Pos[] facePosArray = new Pos[arrayLength];
        for (int i = 0; i < arrayLength; i++) {
            IntPtr currentPtr = IntPtr.Add(posPtr, i * structSize);
            facePosArray[i] = (Pos)Marshal.PtrToStructure(currentPtr, typeof(Pos));
        }

        // 处理检测结果...
    } finally {
        // 必须释放非托管内存,避免内存泄漏
        Marshal.FreeHGlobal(posPtr);
    }
}

额外注意事项

  • 确保C#程序与C++ DLL的架构一致(x86 ↔ x86,x64 ↔ x64),跨架构调用必然会引发内存错误。
  • 若C的Detect函数需要知道数组的最大长度,建议修改C函数定义为int Detect(Pos* pfps, int maxCount),并在C#中同步更新DllImport的参数列表,避免函数越界访问内存。
  • 检查C++中的RECT、POINT是否与Windows SDK的定义一致(确保LONG对应C#的int,因为Windows的LONG始终是32位)。

内容的提问来源于stack exchange,提问作者chao chen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:24:57