C#中调用C的DLL中的回调函数,想实现消息响应机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#中调用C的DLL中的回调函数,想实现消息响应机制相关的知识,希望对你有一定的参考价值。

已经可以通过C#实现DLL中回调函数的调用,代码如下
//实时信息回调函数
[System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute(System.Runtime.InteropServices.CallingConvention.Cdecl)]
public delegate void REALTIME_ROADPOINT_CALLBACK(ref wayPoint_S waypoint, IntPtr arg);

[DllImport("libserviceinterface.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void rs_setcallback_realtime_roadpoint(UInt16 rshd, [MarshalAs(UnmanagedType.FunctionPtr)]REALTIME_ROADPOINT_CALLBACK CurrentPositionCallback, IntPtr arg);

//回调函数
static void CurrentPositionCallback(ref wayPoint_S waypoint, IntPtr arg)

PrintWaypoint(waypoint);


static void PrintWaypoint(wayPoint_S point)

Console.Out.WriteLine("pos.x=0 y=1 z=2", point.cartPos.x, point.cartPos.y, point.cartPos.z);


//主函数
static void Main(string[] args)

//函数指针实例化
REALTIME_ROADPOINT_CALLBACK RobotPosCallBack = new REALTIME_ROADPOINT_CALLBACK(CurrentPositionCallback);
rs_setcallback_realtime_roadpoint(rshd, RobotPosCallBack, IntPtr.Zero);
//打印路点信息
while(true)
PrintWaypoint(waypoint);



但是这种用WHILE循环读取的方式实在不好,能不能有哪位高手指点下如何改为响应消息的机制,最好直接在我的代码上修改一下,谢谢
我主要是想能够实时得到该消息,又不想依靠WHILE循环无休止的跑去获取,类似于中断的方式获取

反过来比较容易,因为C#的P/Invoke就是做这个的。但是显然C++没有关于C#代码回调的的实现,因为C++的架构从来也没考虑过还会调用C#(这种情况实在是太少见了,况且也不必要)。如果非要实现可以采用多进程,使用进程间通信。我想到的:
1.模仿linux中的管道(PIPE),产生虚拟文件,而C++阻塞直到文件产生或本身超时。这样可以实现数据交换,既能实现传数据又可以使C++程序及时得到通知。缺点就是IO密集,降低效率。
2.使用TCP通信,监听端口获取消息,这是跨平台程序通信最常用也是最正统的方法。一般是跨操作系统编程常用的,比如windows和linux ubuntu server程序进行交互。你所述情况虽然不算跨操作系统通信,但是也是跨平台了。而且想想就知道C++作为先出现的语言而且是编译型语言,调用C#托管区的函数还要设回调,其难度可想而知。
3.如果你的C++是基于winform的,可以在C#中P/Invoke调用winAPI的SendMessage发送窗体消息从而间接实现回调。
4.自然还有一些更底层的Dirty方法,比如嵌汇编更改上下文程序指针地址。。。不过不适合正统的程序,因为太Dirty啊。。。而且出错概率大。
除此之外,如果不采用多线程通信,C#的反射(reflection)特性可以参考,但是控制权依然在C#。况且C#无从知道C++的内部结构,自然难以直接调用。你想想吧,是不是这个道理~
参考技术A

你这样写代码不是会疯狂输出吗,下面这样就行了,回调函数只有在需要的时候自动触发

下面是按ESC退出,把你的while循环改一下

while(Console.ReadKey(true).Key != ConsoleKey.Escape)

追问

谢谢回答,我就是希望做成消息响应的方式,但何时消息会传递过来我并不知道啊,如何才能够知道呢。你写的这个是等待我的键盘输入,而不是真的消息传递过来了啊

本回答被提问者采纳

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);  
View Code

 

结构体包含有三个回调函数,并且每个函数也包含有一个结构体(具体不在给出)。上层程序调用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 }  
View Code

 

动态调用流程 

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      }  
View Code

 

技术分享图片
1 public static bool FreeDll() {  
2     if((UInt64)libhandle!=0L)  
3     NaticeMethed.FreeLibrary(libhandle);  
4     return true;          
5 }  
View Code

 

技术分享图片
 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);  
View Code

 

技术分享图片
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 }  
View Code

 

6.  char* 直接使用string,如果有乱码在调整;

7.  尽量不要使用unsafe 

 

以上是关于C#中调用C的DLL中的回调函数,想实现消息响应机制的主要内容,如果未能解决你的问题,请参考以下文章

C#调用C/C++ DLL 参数传递和回调函数的总结

异步回调函数

C#调用C++ dll 回调

C++ dll 回调C# 程序总结

在 C# 中找不到 C++ COM DLL 中的导出函数

C#调用C++动态库接口函数和回调函数