C# Winform 使用委托实现C++中回调函数的功能
在项目中遇到了使用C#调用C++封装的接口,其中C++接口有一个回调函数的参数。参考对比后,在C#中是使用委托(delegate)来实现类似的功能。
下面使用一个示例来介绍具体的使用方式:
第一步:新建C++动态连接库
1.首先新建一个C++动态库程序,如下图所示:
并且在dllmain.cpp文件中添加如下导数函数代码:
typedef int (*FUNC)(int a, int b);
extern "C" __declspec(dllexport) void _stdcall AddNum(int a,int b,FUNC callback)
{
callback(a, b);
}
该函数传入两个整数,并且调用外部的回调函数对这两个整数运算处理。
运行程序生成了相应的DLL
第二步:新建WinForm项目
我新建了一个简单的WinForm程序的界面如下:
由用户输入两个数值,选择相应的运算符,分别有+,-,*,/ 四种运算符,点击计算按钮就得到运算结果。上面我填了10 * 20 = 200;
具体代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;
namespace WinFormCallBackTest
{
public partial class Form1 : Form
{
public delegate int CallBackFunc(int a, int b);
CallBackFunc callback = null;
[DllImport("CallBackDLLTest.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void AddNum(int a,int b,CallBackFunc callback);
public Form1()
{
InitializeComponent();
// 设置窗体启动位置为屏幕中央
this.StartPosition = FormStartPosition.CenterScreen;
callback = new CallBackFunc(CallBackAddNum);
}
public int CallBackAddNum(int a, int b)
{
int nSum = 0;
if (comboBox1.Text == "+")
{
nSum = a + b;
}
else if (comboBox1.Text == "-")
{
nSum = a - b;
}
else if (comboBox1.Text == "*")
{
nSum = a * b;
}
else if (comboBox1.Text == "/")
{
nSum = a / b;
}
// 检查是否需要跨线程操作
if (textBox3.InvokeRequired)
{
// 使用Invoke将操作排队到UI线程
textBox3.Invoke(new Action(() => textBox3.Text = nSum.ToString()));
}
else
{
// 直接更新控件
textBox3.Text = nSum.ToString();
}
return 0;
}
private void button_Calculate_Click(object sender, EventArgs e)
{
int a = 0,b = 0;
Int32.TryParse(textBox1.Text,out a);
Int32.TryParse(textBox2.Text,out b);
AddNum(a,b, CallBackAddNum);
}
}
}
代码说明:
public delegate int CallBackFunc(int a, int b);
CallBackFunc callback = null;
1.上述代码使用delegate声明一个委托,并且将其初始化为null,在构造函数中将回调函数和委托进行绑定。
2.然后再按钮中调用导入的C++接口。
3.在回调函数中将计算结果更新到控件中。
这里有多种方式:
方式1:使用Invoke
或BeginInvoke
方法
Invoke
和BeginInvoke
是.NET提供的用于跨线程更新控件的方法。它们可以确保控件的更新操作在UI线程中执行。
示例代码:
// 检查是否需要跨线程操作
if (textBox3.InvokeRequired)
{
// 使用Invoke将操作排队到UI线程
textBox3.Invoke(new Action(() => textBox3.Text = nSum.ToString()));
}
else
{
// 直接更新控件
textBox3.Text = nSum.ToString();
}
上述代码中
InvokeRequired
:用于检查当前线程是否是控件的创建线程。如果是,则不需要跨线程操作;如果不是,则需要使用Invoke
或BeginInvoke
。Invoke
:同步执行委托,阻塞当前线程,直到操作完成。BeginInvoke
:异步执行委托,不会阻塞当前线程。
方式2:使用SynchronizationContext
SynchronizationContext
是一个更通用的线程同步机制,可以用于在任何线程中同步到UI线程。
示例代码:
// 在主线程中保存SynchronizationContext
private SynchronizationContext _uiContext;
public Form1()
{
InitializeComponent();
_uiContext = SynchronizationContext.Current; // 保存主线程的SynchronizationContext
}
// 在委托回调函数中使用SynchronizationContext更新控件
public void UpdateButtonCallback()
{
_uiContext.Post(_ =>
{
button1.Text = "更新后的文本";
}, null);
}
SynchronizationContext.Current
:获取当前线程的同步上下文。Post
:异步执行委托。
方式3:使用Task
和ContinueWith
(异步任务)
如果你在异步任务中需要更新UI控件,可以使用ContinueWith
方法,并指定在UI线程中执行。
示例代码:
// 假设有一个按钮控件button1和一个异步任务
public void UpdateButtonAsync()
{
Task.Run(() =>
{
// 模拟耗时操作
Thread.Sleep(2000);
}).ContinueWith(t =>
{
button1.Text = "更新后的文本";
}, TaskScheduler.FromCurrentSynchronizationContext()); // 确保在UI线程中执行
}
TaskScheduler.FromCurrentSynchronizationContext
:指定任务继续在UI线程中执行。
总结
- 如果是WinForms应用程序,推荐使用
Invoke
或BeginInvoke
。 - 如果是WPF应用程序,推荐使用
Dispatcher.Invoke
。 - 如果需要更通用的解决方案,可以使用
SynchronizationContext
。 - 如果是异步任务,可以使用
Task
和ContinueWith
。
好了,关于在C#WinForm中使用委托实现回调函数功能的介绍就到这里了。
源码地址:Gitee