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,具体枚举值含义如下图所示:
///
/// 初始化视频解码库
///
/// 通道数 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详解https://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