C# DllImport DLL非托管动态链接库 问题总结

C#程序实现动态调用DLL的研究(转载) - 黄金海岸 - 博客园 (cnblogs.com)https://www.cnblogs.com/goldenocean/archive/2006/09/20/509558.html

C# 调用c++ dll 尝试读取或写入受保护的内存 错误-CSDN社区https://bbs.csdn.net/topics/340067302 步骤:

1,确定是托管动态链接库还是非托管动态链接库;

2,如为C++ 等非托管动态链接库:首先确定 C++ DLL的导出类型是否是_stdcall,默认的是_cdcel,具体枚举值含义如下图所示:

C# DllImport DLL非托管动态链接库 问题总结_第1张图片

/// 
        /// 初始化视频解码库
        /// 
        /// 通道数 7
        /// 监听端口列表,端口号必须是偶数。通道数目和端口数目匹配。例:int[] {8000,8002,...}
        /// 每个通道的队列大小 10.
        /// 图像的宽
        /// 图像的高
        /// 注册C#函数接口到DLL里面。可以为null
        /// 视频转发的目的ip
        /// 当前程序路径,方便同步
        /// 视频文件记录路径
        /// 
        [DllImport(LIBUSB_DLL, SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "setup_channal")]
        internal static extern int setup_channal(uint num, int portList, int buffSize, int w, int h, [MarshalAs(UnmanagedType.FunctionPtr)] CallbackDelegate callback, IntPtr ip , IntPtr dir, IntPtr rec_file_path);
      

首先,应该在C#语言源程序中声明外部方法,其基本形式是:

[DLLImport(“DLL文件”)]

修饰符 extern 返回变量类型 方法名称 (参数列表)

其中

DLL文件:包含定义外部方法的库文件。

修饰符: 访问修饰符,除了abstract以外在声明方法时可以使用的修饰符。

返回变量类型:在DLL文件中你需调用方法的返回变量类型。

方法名称:在DLL文件中你需调用方法的名称。

参数列表:在DLL文件中你需调用方法的列表。

注意:需要在程序声明中使用System.Runtime.InteropServices命名空间。

      DllImport只能放置在方法声明上。

DLL文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。

返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。

若要使用其它函数名,可以使用EntryPoint属性设置,如:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static extern int MsgBox(int hWnd, string msg, string caption, int type);

其它可选的 DllImportAttribute 属性:

CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;

SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;

ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;

PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;

CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;

如上图 存在为个回调函数的参数

        /// 
        /// 注册给C++的委托(函数指针)
        /// 
        /// 
        /// 
        // 不使用如下修饰,会导致C#在调用完后,释放pData内容,导致C程序崩溃;所以在声明代理的时候,说明是C回调,不会收里面资源 
        [System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(System.Runtime.InteropServices.CallingConvention.Cdecl)]
        public delegate void CallbackDelegate(IntPtr array, IntPtr ImgPtr, int size);

1,遇到问题:有时则能运行起来,但会抛出异常:
System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。

解决办法:声明UnmanagedFunctionPointerAttribute属性时 默认是CallingConvention.StdCall,需要确定  C++ DLL的导出类型是否是_stdcall,默认的是_cdcel

 2,参数转换 C++  --> C#  详细浏览下面链接:

        internal const string LIBUSB_DLL = @"avideo_libs\avideo_helper.dll";
        internal const string DIR_PATH = @"./avideo_libs/";
        internal const string REC_FILE_PATH = @"./RecFiles/";

 video_helper.setup_channal(ch, port, 1, width, height, callback[0], Marshal.StringToHGlobalAnsi(turnIP),
                    Marshal.StringToHGlobalAnsi(DIR_PATH), Marshal.StringToHGlobalAnsi(REC_FILE_PATH));//同时转发给

最详细的C++对应C#的数据类型转换 - Innershar - 博客园 (cnblogs.com)https://www.cnblogs.com/innershare/p/10594775.html回调函数

/// 
        /// 解码库被调用接口
        /// 
        /// 图像数据结构体
        /// 当前图像的起始指针
        /// 当前图像的总字节数
        [HandleProcessCorruptedStateExceptions]
        private void CallBackFunction(IntPtr ImgStructPtr, IntPtr ImgPtr, int size)
        {
            try
            {
                if (ImgPtr == IntPtr.Zero || size <= 0)
                {
                    return;
                }
                if (video_helper.Isreleased)
                {
                    return;
                }
                time_start = timeGetTime();

                //if (imageTypeDef.img_ptr == 0)
                //{
                //    imageTypeDef = Marshal.PtrToStructure(ImgStructPtr);
                //}
                /// bitma初始化放在工作线程中,避免跨线程操作
                if (bitmap == null)
                {
                    bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, (IntPtr)ImgPtr);
                    //bytes = new byte[width * height * 4];
                    //rect = new Rectangle(this.Location.X, this.Location.Y, width, height);
                }
                //BitmapData bData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
                //if (ImgPtr != bData.Scan0)
                //{
                //    bData.Scan0 = ImgPtr;
                //}
                //bitmap.UnlockBits(bData);
                this.BeginInvoke(new Action(() =>
                {
                    try
                    {               
                        this.label_nosignal.Visible = false;
                        if (this.CameraPictureBox.InvokeRequired)
                        {
                            Action action = new Action(() => { this.CameraPictureBox.Image = bitmap; });
                            this.BeginInvoke(action);
                        }
                        else
                        {
                            this.CameraPictureBox.Image = bitmap;
                        }
                        if (this.label_nosignal.Visible == true)
                            this.label_nosignal.Visible = false;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message, "PictureBo渲染失败失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "解码库接口Bitmap初始化失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return;
        }

添加HandleProcessCorruptedStateExceptions 属性 可以捕获AccessViolationException类型错误:详细操作见 如下链接:

.NET 4.0 调用 C dll 触发 AccessViolationException 异常的处理方案_于大大大洋的博客-CSDN博客https://blog.csdn.net/arrowzz/article/details/80898718

回调 ImgPtr 参数为当前图像的起始指针, 如果初始化bitmap 在  this.BeginInvoke(new Action(() =>{

  bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, (IntPtr)ImgPtr);

});

中,会抛出异常:
System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。

捕获System.AccessViolationException异常后,会报错:对象当前正在其他地方使用。涉及到C++指针跨线程操作;

解决方法:bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, (IntPtr)ImgPtr);放在回调工作线程中初始化。

(1条消息) C# 中intptr用法_Arno0377的博客-CSDN博客_c# intptr详解icon-default.png?t=M276https://blog.csdn.net/qq_39008744/article/details/106868106?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&utm_relevant_index=2

(1条消息) C++多线程与共享指针_宁静深远的博客-CSDN博客_共享指针 线程安全https://blog.csdn.net/u012477435/article/details/106875121

你可能感兴趣的:(c#)