C++ dll 回调C# 程序总结
Posted lite
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ dll 回调C# 程序总结相关的知识,希望对你有一定的参考价值。
最近开发过程中遇到一个调用C++ dll失败的问题,抛出异常“尝试读取或写入受保护的内存。这通常指示其他内存已损坏。”,
“AccessViolation 0xc0000005”。网上找了一些资料,千篇一律,没能解决问题。由于没办法对dll 进行调试,导致出现什么问题都不清楚。
具体程序是这样的,C++ dll 提供一个导出函数,供上层应用调用,函数参数是一个结构体,
1 [StructLayout(LayoutKind.Sequential, Pack = 1)] 2 public struct InitParam{ 3 public IntPtr hcontext; 4 [MarshalAs(UnmanagedType.FunctionPtr)] 5 public CheckCallBack checkCallback; 6 [MarshalAs(UnmanagedType.FunctionPtr)] 7 public TestCallBack CallBack; 8 [MarshalAs(UnmanagedType.FunctionPtr)] 9 public CheckCtrlCallBack checkCtrlCallBack; 10 } 11 12 public delegate uint CheckCallBack(IntPtr hcontext,ref CheckRec Check); 13 14 public delegate uint TestCallBack(IntPtr hcontext,ref TestRec test); 15 16 public delegate uint CheckCtrlCallBack(IntPtr hcontext, uint dwType,ref CheckCtrlRec ctrl);
结构体包含有三个回调函数,并且每个函数也包含有一个结构体(具体不在给出)。上层程序调用dll 的导出函数,
把包含函数指针的InitParam 结构体传给dll, dll 处理完数据后会自动调用相应的 回调函数触发上层程序(C#程序事件被触发)。
总结如下:
1.结构体基本采用 [StructLayout(LayoutKind.Sequential, Pack = 1)] 的属性声明,涉及函数指针的要声明 [MarshalAs(UnmanagedType.FunctionPtr)] 属性;
2. CallBack 函数采用委托的形式;
3. 所有涉及结构体的都要加 ref (重要)
4 . 调用导出函数 可以采用动态和静态的两种方式,静态写起来比较方便,动态麻烦点
1 //[DllImport("Test.dll")] 2 //public extern static uint Test(ref InitParam param); 3 4 public static class NaticeMethed 5 { 6 [DllImport("kernel32.dll")] 7 public extern static IntPtr LoadLibrary(string path); 8 9 [DllImport("kernel32.dll")] 10 public extern static IntPtr GetProcAddress(IntPtr lib, string funcName); 11 12 [DllImport("kernel32.dll")] 13 public extern static bool FreeLibrary(IntPtr lib); 14 }
动态调用流程
1. 载入dll,
2. 获取要调用的函数指针
3. 使用 Marshal.GetDelegateForFunctionPointer 把函数指针转换为委托形式。
4. 触发委托实例
5. 程序结束前把dll free掉
1 public delegate uint Test(ref InitParam param); 2 private static IntPtr libhandle; 3 public static IntPtr LoadDll() { 4 // 5 libhandle = NaticeMethed.LoadLibrary(@"Test.dll"); 6 if ((UInt64)libhandle == 0L) { 7 throw new Exception("没有载入Test.dll,请检查dll路径"); 8 } 9 10 IntPtr funchandle = NaticeMethed.GetProcAddress(libhandle, "Test"); 11 if ((UInt64)funchandle == 0L) { 12 NaticeMethed.FreeLibrary(libhandle); 13 throw new Exception("Test 初始化失败"); 14 } 15 return funchandle; 16 }
1 public static bool FreeDll() { 2 if((UInt64)libhandle!=0L) 3 NaticeMethed.FreeLibrary(libhandle); 4 return true; 5 }
1 IntPtr ptr = LoadDll(); 2 if ((UInt64)ptr == 0L) { 3 throw new Exception("初始化dll失败"); 4 } 5 Test test= (Test)Marshal.GetDelegateForFunctionPointer(ptr, typeof(Test)); 6 7 InitParam Init; 8 Init.hcontext = this.Handle; 9 Init.CheckCallback = new CheckCallBack(LoginForm_CheckCallback); 10 Init.TestCallBack = null; 11 Init.CheckCtrlCallBack = new CheckCtrlCallBack(LoginForm_CheckCtrlCallBack);
1 try { 2 Test(ref Init); // a Test Dialogs 3 } 4 catch(Exception ex) { 5 Console.WriteLine(ex.Message); 6 } 7 finally { 8 FreeDll(); 9 }
6. char* 直接使用string,如果有乱码在调整;
7. 尽量不要使用unsafe
以上是关于C++ dll 回调C# 程序总结的主要内容,如果未能解决你的问题,请参考以下文章
将带有参数的 c# 回调方法传递给 c++ dll 会导致 System.ExecutionEngineException
在通过 P/Invoke 获得的 C++ 结构上设置 C# 回调