实例1
C#上位机+后台C应用线程
目录
1. 需求
1.1 使用场景
1. 工控行业
在工业控制系统中,C#通常用于开发用户界面(UI)和数据监控、分析功能。而C/C++则负责与硬件设备(如PLC、传感器等)进行实时通信和数据处理。通过 C# 调用 C/C++ 生成的 DLL(动态链接库),可以将 C/C++ 实现的高效控制算法、实时数据处理、复杂计算等能力集成到 C# 上位机应用中,从而实现高性能与易用性的平衡。
2. 视觉行业
在机器视觉系统中,图像处理算法往往需要大量的计算和优化,这些功能通常由C/C++编写,因为C/C++能够充分利用硬件性能,提供高效的图像处理能力。而C#则用于构建用户界面,展示处理结果,进行控制和管理。C#通过调用 C/C++ 编写的 DLL,能够利用 C/C++ 实现的图像处理和计算能力,同时利用 C# 提供的便利性与可扩展性,简化上位机的开发。
3. 人工智能行业
在人工智能领域,尤其是在深度学习、机器学习等复杂算法的实现上,C/C++因其高性能常常作为核心算法的开发语言。而C#则可以作为前端界面,提供数据输入、结果展示、参数配置等功能。通过 C# 调用 C/C++ 算法库,能够将C/C++中实现的AI模型和算法高效集成到C#上位机应用中,实现强大的人工智能功能和良好的用户体验。
4. 数据采集与控制系统
在一些需要高频率、低延迟数据采集与控制的场合,例如实验室自动化系统、机器人控制系统等,C/C++通常承担实时性要求较高的任务。C#则提供前端展示和控制面板,供操作人员查看数据、调整参数或进行指令控制。C#通过调用C/C++编写的后台线程或DLL,确保系统能够在满足实时性要求的同时,提供清晰的用户界面和控制功能。
5. 嵌入式软件的开发测试仿真
嵌入式系统的开发往往需要在没有物理硬件或硬件设备还未完成时进行仿真。C#可以作为上位机开发语言,通过图形界面展示嵌入式系统的模拟状态,允许开发人员模拟外部设备的行为、模拟输入信号,并观察仿真结果。C/C++则用来实现嵌入式系统中的核心算法和硬件模拟,如模拟微控制器的I/O操作、定时器、PWM信号等功能。C#通过调用C/C++编写的DLL或通过多线程技术与C/C++仿真模块交互,确保仿真环境的高效性与可靠性。
1.2 关联工具
C/C++ DLL开发平台:Visual Studio 2022
C# 上位机开发平台:Visual Studio 2022
基本能力要求:C/C++ 项目开发、C# 上位机开发、
1.3 实例需求概述
C工程
现有”App.c”文件,其代码如下:
#include <stdio.h>
int x;//输入量
int y;//监控量
int main()
{
int k=0;
while(1)
{
y=x+k;
printf("y==%d\n",y);
k++;
}
return 0;
}
C# 上位机
在C# 界面开发中,"textBox1"中输入x的值,点击"button1",调度运行C文件中函数main(),并每隔1秒获取y的值显示在"textBox2"中。点击"button2",终止函数main()的运行。注意函数main()中有 while 循环,建议多线程。
1.4 实现步骤设计
Step1. 设计 C# 界面:包括 `textBox1`(用于输入 `x` 的值),`textBox2`(用于显示 `y` 的值),`button1`(启动函数),`button2`(停止函数)。
Step2. 实现多线程:使用 `System.Threading` 中的 `Thread` 类来异步运行 C 的 `main()` 函数中的 `while` 循环。
Step3. 使用 C# 中的锁定机制:防止 UI 线程和后台线程之间的数据竞争,确保安全地更新 UI 控件。
Step4. 使用 `CancellationToken` 终止线程:点击按钮 2 时能够终止 `while` 循环中的线程。
Step5. 调用C/C++项目在 Visual Studio 生成的DLL 。
2. C/C++项目
2.1 创建项目
1.新建一个C++项目,选择 “动态链接库”
2.项目名称 -- “C_DLL”,位置-- “C:\Users\Administrator\Desktop” ,勾选“将解决方案和项目放在同一目录中”。 点击“创建”
3.创建成功后,有自动生成的默认代码 “dllmain.cpp”
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
将该 “dllmain.cpp” 中 代码替换如2.2 中所示。
2.2 C/C++代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <pch.h>
/*-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/
//动态库dll工程自动生成的内容
//BOOL APIENTRY DllMain( HMODULE hModule,
// DWORD ul_reason_for_call,
// LPVOID lpReserved
// )
//{
// switch (ul_reason_for_call)
// {
// case DLL_PROCESS_ATTACH:
// case DLL_THREAD_ATTACH:
// case DLL_THREAD_DETACH:
// case DLL_PROCESS_DETACH:
// break;
// }
// return TRUE;
//}
/*-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------*/
//头文件.h
#include <iostream>
using namespace std;
#include <stdio.h>
#include "stdlib.h"
#include <tchar.h>
/*-----------------------------------------------------------------------*/
//全局变量
int X;//while()控制状态值
int Y;//C工程模块输出值
/*-----------------------------------------------------------------------*/
// C工程主接口函数
extern "C" __declspec(dllexport) int APPMain(int value_in)
{
/*-----------------------*/
//**** C工程软件标签 ****//
cout << " " << endl; cout << " " << endl; cout << " " << endl;
cout << "//**********************************************//" << endl;
cout << "This is the C progect!" << endl;
cout << "//**********************************************//" << endl;
cout << " " << endl; cout << " " << endl; cout << " " << endl;
//---------------------------//
printf("AABBCCDD!\n");//调用DLL printf 在DLL函数调用结束后才有相关打印内容
//---------------------------//
int k = 0;
while (X == 1)
{
Y = k + value_in;
//cout << "Y==" << Y << endl;
k++;
Sleep(20);//设置C工程 机器周期为20毫秒
}
return 0;
}
/*-----------------------------------------------------------------------*/
// 获取 全局变量 Y 的状态值
extern "C" __declspec(dllexport) int GetOut_Y(void)
{
//cout << "GetOut_Y==" << Y << endl;
return Y;
}
/*-----------------------------------------------------------------------*/
// 设置 全局变量 X 的状态值
extern "C" __declspec(dllexport) int GetIn_X(int value_in)
{
X = value_in;
cout << "GetIn_X==" << X << endl;
return X;
}
/*-----------------------------------------------------------------------*/
2.3 生成DLL
1. 配置活动平台为 “x64” (C# 中也要设置为 x64),统一项目环境。
2.点击 “生成” -- “生成解决方案” ,输出框显示 成功。
3.输出 DLL 文件 位置 “C:\Users\Administrator\Desktop\C_DLL\x64\Debug”
3. C#项目
3.1 新建项目
1. 新建一个C#项目,选择 “Windows窗体应用(.NET Framework)”
2.项目名称 -- “C#_Call_C” ,位置 -- “C:\Users\Administrator\Desktop” ,勾选 “将解决方案和项目放在同一目录中” ,框架 -- “.NET Framework 4.7.2” 。 点击 “创建”
3.点击 选项卡 “视图” -- “工具箱” ,使左侧挂载 “工具箱” 控件。
3.2 窗体UI控件设计
上位机窗体UI控件
C#_UI控件配置
3.3 C#代码
“Form1.cs” 中的
元素:
private int C_Program_Status = 1;//C工程调度状态; 0--未触发调度;1--触发调度
private int Get_Count;//获取dll数据 计时器的计数值
private CancellationTokenSource app_cts;//用于控制线程终止的令牌。
private Thread workerThread;//创建的工作线程
#region 调用C工程函数 dll
public class DemoDelegate
{
[DllImport("C_DLL.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int APPMain(int x);
[DllImport("C_DLL.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int GetOut_Y();
[DllImport("C_DLL.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern int GetIn_X(int X);
}
#endregion
函数:
public Form1()
private void button1_Click(object sender, EventArgs e)
private void RunMain(int x, CancellationToken token)
private void timer_GetDate(object sender, EventArgs e)
public Form1()
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
/*-----------------------------------------------------------------------*/
private void button1_Click(object sender, EventArgs e)
{
int x;
//[调度C工程]按钮点击触发
if (C_Program_Status==0)
{
/*-----------------------*/ //UI控件 设置
button1.BackColor = System.Drawing.Color.Gray;
button1.Text = "关闭C工程";
/*-----------------------*/
C_Program_Status = 1;
DemoDelegate.GetIn_X(0);//C工程 while() 中断
app_cts?.Cancel();// 请求停止线程;确保app_cts不为 null,避免发生空引用异常。
this.timer_GetCprogram.Enabled = false;//关闭计时器
/*-----------------------*/
}
else
{
// 点击按钮1,启动线程
if (int.TryParse(textBox1.Text, out x))
{
/*-----------------------*/ //UI控件 设置
button1.BackColor = System.Drawing.SystemColors.ButtonFace;
button1.Text = "调度C工程";
/*-----------------------*/ //
this.timer_GetCprogram.Enabled = true;//打开计时器
this.Get_Count = 0;
C_Program_Status = 0;
/*-----------------------*/
// 创建一个取消令牌源
app_cts = new CancellationTokenSource();
// 启动后台线程来执行main()中的逻辑
DemoDelegate.GetIn_X(1);
workerThread = new Thread(() => RunMain(x, app_cts.Token));
workerThread.Start();
/*-----------------------*/
}
else
{
MessageBox.Show("请输入有效的x值!");
}
}
}
/*-----------------------------------------------------------------------*/
private void button2_Click(object sender, EventArgs e)
/*-----------------------------------------------------------------------*/
private void button2_Click(object sender, EventArgs e)
{
/*-----------------------*/ //UI控件 设置
//[清空]按钮点击触发 清空输入与输出值
textBox1.Clear();
textBox2.Text = "";
button1.BackColor = System.Drawing.Color.Gray;
button1.Text = "关闭C工程";
/*-----------------------*/
C_Program_Status = 1;
DemoDelegate.GetIn_X(0);//C工程 while() 中断
app_cts?.Cancel();// 请求停止线程
this.timer_GetCprogram.Enabled = false;//关闭计时器
/*-----------------------*/
}
/*-----------------------------------------------------------------------*/
private void RunMain(int x, CancellationToken token)
/*-----------------------------------------------------------------------*/
// 模拟C语言main()中的功能,传入值x
private void RunMain(int x, CancellationToken token)
{
Debug.WriteLine("--- 启动后台线程 --");
if (!token.IsCancellationRequested)
{
Debug.WriteLine("*** Start C progect ***");
DemoDelegate.APPMain(x);
//Invoke(new Action(() => textBox2.Text = y.ToString()));//更新textBox2中的y值(要使用Invoke来访问UI控件)
Debug.WriteLine("*** End of C progect run! ***");
}
/*-----------------------*/ //另一种配置方案
//int k = 0;
//while (!token.IsCancellationRequested)
//{
// // 计算y = x + k
// int y = x + k;
// // 更新textBox2中的y值(要使用Invoke来访问UI控件)
// Invoke(new Action(() => textBox2.Text = y.ToString()));
// // 每20毫秒更新一次
// Thread.Sleep(20);
// k++;
//}
/*-----------------------*/
}
private void timer_GetDate(object sender, EventArgs e)
/*-----------------------------------------------------------------------*/
//获取获取dll数据计时器
private void timer_GetDate(object sender, EventArgs e)
{
this.Get_Count++;
if((Get_Count%50==0)&&(C_Program_Status==0))//每20*50=1000毫秒,刷新获取的数据
{
int y = 0;
y = DemoDelegate.GetOut_Y();
textBox2.Text = y.ToString();
Debug.WriteLine("Get_Count=={0}, y=={1}", Get_Count, y);
}
}
/*-----------------------------------------------------------------------*/
3.4 配置与调试
1. 配置活动平台为 “x64” (C# 中也要设置为 x64),统一项目环境。
2.点击 “生成” -- “生成解决方案” ,输出框显示 成功。
3.将 “C_DLL” 生成的 .dll 文件,拷贝至 C#_call_C 文件的 “C#_call_C\bin\x64\Debug” 路径下,以便做工程的调试。
4.将 “C_DLL” 生成的 .dll 文件,拷贝至 C#_call_C 文件的 “C#_call_C\obj\x64\Debug” 路径下,以便做工程的独立运行。
5.调试态如下动图所示
总结
C# 上位机 + 后台 C/C++ 应用的优势:
-- 性能和效率: C/C++负责处理计算密集型的任务,如图像处理、数据分析和实时控制等,能够充分利用硬件资源并提供高性能。
-- 可扩展性与开发效率: C#拥有丰富的开发工具和库,特别是在UI设计、数据处理和网络通信方面。它使得上位机界面的开发更加便捷,可以快速搭建用户交互界面。
-- 多线程与异步处理: 在复杂的工业系统中,后台C/C++线程可以处理实时数据采集和控制指令,而C#则通过调用后台线程提供的数据,实时更新UI,确保界面不会因阻塞而卡顿。
-- 模块化与解耦: 将C/C++算法封装为DLL,使得C#与C/C++代码之间的依赖关系最小化,便于维护和升级。同时,C/C++和C#的分工明确,互不干扰,便于团队协作。
通过这种结合方式,上位机不仅能够充分利用C/C++的高效性能,还能通过C#简化开发过程,提升用户体验,在许多高性能计算和工业控制系统中具有重要的应用价值。
//--------------------------------------------------------------------------------------------------------------------------//
2025年1月14日
By:Mr.C
//--------------------------------------------------------------------------------------------------------------------------//
标签:--,C++,DLL,C#,int,线程 From: https://blog.csdn.net/weixin_40978412/article/details/145141601