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

使用P/Invoke调用C++ SDK时程序直接退出无报错问题排查

.NET7 WinForm调用C++硬件SDK P/Invoke问题排查

问题背景

我基于.NET7 WinForm开发硬件交互程序,计划通过P/Invoke调用C++ SDK实现硬件交互,但调用MV_CODEREADER_EnumDevices接口后程序直接退出,即使使用try&catch也无法捕获错误。以下是我编写的代码:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

        }

        private void Form1_Load(object sender, EventArgs e)
        {            
            var i = new MV_CODEREADER_DEVICE_INFO_LIST();
            var j = MV_CODEREADER_EnumDevices(ref i, 1);
        }
        [DllImport("MvCodeReaderCtrl.dll")]
    public static extern Int32 MV_CODEREADER_EnumDevices(ref MV_CODEREADER_DEVICE_INFO_LIST pstDevList, UInt32 nTLayerType);

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public unsafe struct MV_CODEREADER_USB3_DEVICE_INFO
    {
        public byte CrtlInEndPoint;
        public byte CrtlOutEndPoint;
        public byte StreamEndPoint;
        public byte EventEndPoint;
        public UInt16 idVendor;
        public UInt16 idProduct;
        public uint nDeviceNumber;
        public byte chDeviceGUID;
        public byte chVendorName;
        public byte chModelName;
        public byte chFamilyName;
        public byte chDeviceVersion;
        public byte chManufacturerName;
        public byte chSerialNumber;
        public byte chUserDefinedName;
        public uint nbcdUSB;
        public fixed uint nReserved[3];
    }
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public unsafe struct MV_CODEREADER_GIGE_DEVICE_INFO
    {
        public UInt32 nIpCfgOption;
        public UInt32 nIpCfgCurrent;
        public UInt32 nCurrentIp;
        public UInt32 nCurrentSubNetMask;
        public UInt32 nDefultGateWay;
        public fixed byte chManufacturerName[32];
        public fixed byte chModelName[32];
        public fixed byte chDeviceVersion[32];
        public fixed byte chManufacturerSpecificInfo[48];
        public fixed byte chSerialNumber[16];
        public fixed byte chUserDefinedName[16];
        public UInt32 nNetExport;
        public UInt32 nCurUserIP;
        public fixed uint nReserved[3];
    }
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public unsafe struct MV_CODEREADER_DEVICE_INFO
    {
        public UInt16 nMajorVer;
        public UInt16 nMinorVer;
        public uint nMacAddrHigh;
        public uint nMacAddrLow;
        public uint nTLayerType;
        public fixed uint nReserved[3];
        public MV_CODEREADER_GIGE_DEVICE_INFO stGigEInfo;
        public MV_CODEREADER_USB3_DEVICE_INFO stUsb3VInfo;
    }
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MV_CODEREADER_DEVICE_INFO_LIST
    {
        public UInt32 nDeviceNum;
        public MV_CODEREADER_DEVICE_INFO[] pDeviceInfo;
    }

    }
}

代码错误分析

  • 字符串字段定义错误:MV_CODEREADER_USB3_DEVICE_INFO中的chDeviceGUID、chVendorName等字段,原C++ SDK应为字符数组类型,但你定义成了单个byte,导致内存结构完全不匹配,触发内存访问错误。
  • 设备列表结构体类型不匹配:MV_CODEREADER_DEVICE_INFO_LIST中的pDeviceInfo在C++中是指向设备信息结构体的指针,而你定义成了C#数组,非托管代码无法正确写入内存,直接引发崩溃。
  • 未提前分配内存:调用MV_CODEREADER_EnumDevices前未初始化设备数量或分配对应内存,非托管代码无法找到合法的内存区域写入数据。
  • 非托管异常无法被托管代码捕获:P/Invoke调用的非托管代码抛出的原生异常,C#的try&catch无法捕获,需要开启混合模式调试才能查看错误详情。
  • 内存对齐可能不匹配:虽然指定了Pack=1,需确认C++ SDK的结构体是否采用相同的内存对齐规则,否则会导致内存偏移错误。

修正后的代码示例

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 开启混合模式调试才能查看非托管异常
            try
            {
                // 第一步:调用接口获取设备数量
                MV_CODEREADER_DEVICE_INFO_LIST deviceList = new MV_CODEREADER_DEVICE_INFO_LIST();
                int result = MV_CODEREADER_EnumDevices(ref deviceList, 1);
                if (result != 0)
                {
                    MessageBox.Show($"获取设备数量失败,错误码:{result}");
                    return;
                }

                // 第二步:根据设备数量分配非托管内存
                if (deviceList.nDeviceNum > 0)
                {
                    int deviceInfoSize = Marshal.SizeOf(typeof(MV_CODEREADER_DEVICE_INFO));
                    IntPtr deviceInfoPtr = Marshal.AllocHGlobal(deviceInfoSize * (int)deviceList.nDeviceNum);
                    deviceList.pDeviceInfo = deviceInfoPtr;

                    // 再次调用获取完整设备信息
                    result = MV_CODEREADER_EnumDevices(ref deviceList, 1);
                    if (result == 0)
                    {
                        // 遍历解析每个设备信息
                        for (uint i = 0; i < deviceList.nDeviceNum; i++)
                        {
                            IntPtr currentDevicePtr = IntPtr.Add(deviceInfoPtr, deviceInfoSize * (int)i);
                            MV_CODEREADER_DEVICE_INFO deviceInfo = Marshal.PtrToStructure<MV_CODEREADER_DEVICE_INFO>(currentDevicePtr);
                            // 可在这里提取并显示设备信息,比如串口号、IP等
                        }
                        MessageBox.Show($"成功找到 {deviceList.nDeviceNum} 台设备");
                    }
                    else
                    {
                        MessageBox.Show($"获取设备信息失败,错误码:{result}");
                    }

                    // 释放非托管内存,避免泄漏
                    Marshal.FreeHGlobal(deviceInfoPtr);
                }
                else
                {
                    MessageBox.Show("未找到任何设备");
                }
            }
            catch (Exception ex)
            {
                // 仅能捕获托管代码异常,非托管异常需依赖混合调试
                MessageBox.Show($"托管代码异常:{ex.Message}");
            }
        }

        // 明确调用约定,需与C++ SDK保持一致,若为stdcall则改为CallingConvention.StdCall
        [DllImport("MvCodeReaderCtrl.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int MV_CODEREADER_EnumDevices(ref MV_CODEREADER_DEVICE_INFO_LIST pstDevList, uint nTLayerType);

        // 修正USB设备信息结构体:字符串字段改为对应长度的字符数组
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public unsafe struct MV_CODEREADER_USB3_DEVICE_INFO
        {
            public byte CrtlInEndPoint;
            public byte CrtlOutEndPoint;
            public byte StreamEndPoint;
            public byte EventEndPoint;
            public ushort idVendor;
            public ushort idProduct;
            public uint nDeviceNumber;
            public fixed byte chDeviceGUID[64]; // 需对照SDK文档调整实际长度
            public fixed byte chVendorName[64];
            public fixed byte chModelName[64];
            public fixed byte chFamilyName[64];
            public fixed byte chDeviceVersion[64];
            public fixed byte chManufacturerName[64];
            public fixed byte chSerialNumber[64];
            public fixed byte chUserDefinedName[64];
            public uint nbcdUSB;
            public fixed uint nReserved[3];
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public unsafe struct MV_CODEREADER_GIGE_DEVICE_INFO
        {
            public uint nIpCfgOption;
            public uint nIpCfgCurrent;
            public uint nCurrentIp;
            public uint nCurrentSubNetMask;
            public uint nDefultGateWay;
            public fixed byte chManufacturerName[32];
            public fixed byte chModelName[32];
            public fixed byte chDeviceVersion[32];
            public fixed byte chManufacturerSpecificInfo[48];
            public fixed byte chSerialNumber[16];
            public fixed byte chUserDefinedName[16];
            public uint nNetExport;
            public uint nCurUserIP;
            public fixed uint nReserved[3];
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public unsafe struct MV_CODEREADER_DEVICE_INFO
        {
            public ushort nMajorVer;
            public ushort nMinorVer;
            public uint nMacAddrHigh;
            public uint nMacAddrLow;
            public uint nTLayerType;
            public fixed uint nReserved[3];
            public MV_CODEREADER_GIGE_DEVICE_INFO stGigEInfo;
            public MV_CODEREADER_USB3_DEVICE_INFO stUsb3VInfo;
        }

        // 修正设备列表结构体:pDeviceInfo改为IntPtr对应C++指针
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MV_CODEREADER_DEVICE_INFO_LIST
        {
            public uint nDeviceNum;
            public IntPtr pDeviceInfo;
        }
    }
}

额外注意事项

  • 确认字段长度:chDeviceGUID等字符数组的长度需严格对照C++ SDK的定义,避免内存溢出。
  • 调用约定匹配:若C++ SDK函数使用__stdcall调用约定,需将DllImport中的CallingConvention改为CallingConvention.StdCall。
  • 开启混合调试:在Visual Studio项目属性→调试中勾选“启用本机代码调试”,才能捕获非托管代码的内存错误和异常。
  • DLL路径正确:将MvCodeReaderCtrl.dll及依赖的SDK文件放到程序输出目录,或通过SetDllDirectory指定DLL搜索路径。

内容的提问来源于stack exchange,提问作者Melon NG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:24:51