欲看图文版pdf,请电邮14518918@qq.com。
声明,本笔记大量引用了AI对话内容。本笔记中的代码,基于Windows 11 + Visual Studio 2026 + .NET 10.0验证。
从CVI到C#有三个最关键的思维转变:
UI是代码,不是资源:Form1.Designer.cs 就是UI的“代码描述”,它和你的业务逻辑代码Form1.cs共同组成一个完整的类。
万物皆对象:你操作的按钮、字符串、数组,甚至错误信息,都是“对象”,有自己属性和方法。不再是CVI里那种“函数+句柄”的分离感。
编译器帮你做更多检查:C#是强类型语言,编译器会检查事件绑定、类型转换等更多错误。这让程序更健壮,但也意味着你必须更严谨。
如下是CVI与C#的核心概念对照表
概念维度 CVI (ANSI C) 的习惯 C# (.NET) 的对应概念 关键区别与点拨
程序入口 int main (void) static void Main(string[] args) C#入口在类里,是静态方法。CVI的main是全局的。
界面构建 拖拽控件,生成.uir文件,代码里用LoadPanel加载。 拖拽控件,生成Form1.Designer.cs和.resx文件。 核心变化:CVI的UI是资源文件(.uir),代码里通过句柄操作;C#的UI是类代码(.Designer.cs),是程序的一部分,编译在一起。
事件响应 用SetCtrlAttribute或回调函数绑定,比如InstallCtrlCallback(panel, PANEL_BUTTON, ButtonCallback, 0); 使用委托(Delegate)和事件(Event),如button1.Click += Button1_Click; C#的事件是类型安全的方法指针,更安全、更规范。CVI的回调更像函数指针。
变量声明 int count; 在函数或文件作用域声明。 必须指定访问修饰符,如private int count; C#更强调封装,即使最简单的变量也要考虑它属于谁(public/private)。
数组操作 固定大小数组,int arr[10];,通过索引访问。 数组是对象,有属性(Length),还有ArrayList、List等集合。 C#的数组更“智能”,集合类更强大,但需要理解泛型等概念。
字符串处理 字符数组char str[],使用strcpy、sprintf等函数。 string是基本类型,直接支持+、Contains()、Substring()等方法。 转变最大:C#的字符串操作极其方便,告别strcat和缓冲区溢出的烦恼。
内存管理 手动malloc和free。 垃圾回收(GC)自动管理,不再需要手动释放。 C#程序员很少直接写delete,但需要了解GC对性能的影响(如IDisposable)。
错误处理 检查返回值(如-1),或使用SetErrorHandler。 异常处理(try-catch-finally),强制处理或上抛。 C#强制你用try-catch,能写出更健壮的程序,但学习成本稍高。
文件与资源 用fopen、fread等C标准库函数,或IniFile等CVI专用函数。 使用System.IO命名空间下的类,如File、StreamReader。 统一在System.IO下,概念更统一,功能更丰富。
多线程 使用CmtScheduleThreadPoolFunction等CVI线程池函数。 使用System.Threading命名空间,包含Thread、Task等。 C#的Task和async/await模型更现代、更易用,但概念也更新。
项目组织 项目文件(.prj)包含源文件、库、.uir文件。 解决方案(.sln)和项目文件(.csproj)分开。 C#用解决方案管理多个项目,结构更清晰,适合大工程。
编译与部署 编译成.exe,依赖CVI引擎库。 编译成.exe或.dll,依赖.NET运行时(.NET Framework/Core)。 现代C#(.NET Core/.NET 5+)支持自包含发布,无需安装庞大引擎库。
1, 创建C#新项目
在VS中为C#创建一个新项目,选第一个.NET最新框架的winform应用模板。
起一个项目名称QOSAEVB,位置就是D:,勾选“将解决方案和项目放在同一目录”。
至此,我们得到一个空面板的项目。同时在D:\QOSAEVB目录下,得到一个C# winforms工程文件夹。
为了用Git管理代码,拷贝一个C#项目适用的.gitignore文件过来(它规定了哪些文件和文件夹不用上传),然后空白处右键选“Git Create repository here…”,新增一个.git隐藏子目录。
为了访问SQLite数据库,还得把SQLite_QOSAEVB.db这个文件拷贝到工程文件夹。然后在VS右侧的解决方案资源管理器中,选中这个.db并在右下角的属性窗,设置“赋值到输出目录”为“如果较新则复制”。
为了访问CH341接口的板子,还得把CH341DLLA64.DLL和40GPOWER_A64_DLL.dll这两个文件拷贝到工程文件夹。然后在VS右侧的解决方案资源管理器中,选中这两个DLL并在右下角的属性窗,设置“复制到输出目录”为“如果较新则复制”。
为了访问SQLite数据库,需要给本工程添加一个包Microsoft.Data.Sqlite。在VS右侧的解决方案资源管理器中的“依赖项”点右键,选“管理NuGet包”,然后在左侧窗口的“浏览”标签页的搜索栏填入“Microsoft.Data.Sqlite”回车,选中Microsoft.Data.Sqlite这一行,点“安装”,成功安装后就能在解决方案资源管理器中的“依赖项”下面的“包”中,看到新增了一条“Microsoft.Data.Sqlite(10.0.0.11)”。
至此,项目框架搭建完成。
2, CVI的uir界面移植
在CVI2010中,菜单Tools-- UI to Code Converter…,可以将CVI的.uir面板保存为文本文件.tui。tui中保存有面板的全部面板、菜单栏、控件的几乎所有属性,和.uir等价,只是存储格式不同:
1、Panel(面板窗口)信息
- 面板常量名:
PANEL(生成 h 文件的宏名) - 窗口位置、宽高、标题文字、背景色
- 面板样式:是否可缩放、是否标题栏、模态、初始显示状态、回调函数指针名称
- 面板字体属性
2、每一个控件完整信息(按钮、LED、Graph、文本框、旋钮、列表等)
每个控件全部属性逐条记录:
- 控件 ID 常量名:
PANEL_BUTTON(.h头文件的宏) - 控件类型(Button、LED、Numeric、Graph…)
- 控件 XY 坐标、宽高
- 显示文本、标签、颜色、字体、可见 / 禁用状态
- 回调函数名字符串 :就是你填的回调函数名称,例如
ButtonCallback - 各类控件特有属性:按钮样式、Graph 坐标轴设置、列表条目、热键、默认值等。
向deepseek拖拽CVI的.tui文件,要求AI转成C#格式的form1.cs和form1.designer.cs源代码,然后手工覆盖到相应源文件后,应该就能在VS中重新打开Form1.cs[设计],观察到AI还原的面板布局了。当然,CVI和C#是两套IDE,你还要干两件事:处理Graph、Switch、LED、Table这类winforms没有的原生控件(可换成C#工具箱中的Chart、RadioButton、PictureBox、DataGridView来替代),最后把CVI的回调函数移植成C#的事件方法。
注意,面板属性的AutoScaleMode推荐配置成None,即可关闭WinForms的自动缩放功能,窗体、控件大小完全使用设计器写死的像素,不跟随系统 DPI、系统字体大小自动放大缩小,以避免exe跑在别人电脑上得Windows是高DPI时,整套UI控件错位跑位。
3, 为访问硬件的DLL新增接口类
为了方便调试DLL函数,可以新建一个DLLProxy类,把所有DLL函数都定义成public static extern的方法,那么在form1.cs中就可以直接用DLLProxy.DLLFunctionName()这样的代码,去调用这个类的静态方法(CVI眼中的DLL函数)。
但也有三个缺点:
public static extern直接对外暴露底层原生 DLL 接口;上层 UI 可以直接调用裸 P‑Invoke,没有任何参数校验、异常捕获;- 如果传非法句柄、错误参数,直接底层 DLL 崩溃,C# 层拦不住;
- DLL缺失的时候,第一次调用该函数瞬间抛出
DllNotFoundException,如果没有 try‑catch,整个程序直接闪退。
推荐调试跑通之后再重构,将底层public static extern全部改为 private static extern,不对外暴露;再写一层普通public static C# 包装函数,在包装函数里面做:参数合法性检查、捕获 DLL 缺失异常、日志、错误码封装。
先用public跑通再说,右键点击VS右侧的“解决方案资源管理器”里的项目名,添加一个类再另存为DLLProxy.cs。注意按照下面的格式构建文件头,然后把跟硬件交互的 40GPOWER_A64_DLL.dll和CH341A_DLL.dll翻译成C#能看懂的接口(把.h头文件给到AI就能得到)。
using System;
using System.Runtime.InteropServices;
namespace QOSAEVB // 注意:这里的命名空间要和你的 Form1.cs 里一样
{
public class DLLProxy
{
//动态调用DLL函数头声明
[DllImport(“CH341DLLA64.dll”, CallingConvention = CallingConvention.StdCall)]
public static extern ulong CH341GetVersion();
//这里接着拷贝DLL函数头声明}
4, 为访问数据库的SQLite新增接口类
为了方便管理SQLite数据库的读写,还有数据跟面板控件的交互,需要新开一个mySQLite.cs文件,新建一个ConfigData类,对应数据库config表单即配置数据,和TestData类,对应数据库result表单即测试数据。
using System;
using Microsoft.Data.Sqlite;
namespace QOSAEVB // 注意:这里的命名空间要和你的 Form1.cs 里一样
{
public class ConfigData
{
//这里填写属性和方法
}
public class TestData { //这里填写属性和方法 }}
注意,Microsoft.Data.Sqlite默认不是线程安全的,多线程同时写入或读取同一个连接会报错。所以,建议所有数据库操作必须通过同一个静态方法,内部加锁。
注意,原先在CVI里面,读到了外设数据,随即就显示到界面的做法,仅适用于CVI。C#编程并不推荐这样干,要轻耦合,最好是用事件通知机制来刷新界面。比如,SerialPort.DataReceived事件在非UI线程(即后台线程)上触发。如果直接在事件处理函数中访问TextBox等控件,会抛出异常InvalidOperationException。正确的做法是使用Control.Invoke或BeginInvoke将UI更新操作封送到UI线程执行。下面是经典的递归调用Invoke模式,足以线程安全地更新任意控件的Text属性的实例:
///
/// 更新任意控件的Text属性(线程安全)
///
private void SafeSetText(Control ctrl, string text)
{
// 步骤1:检查当前线程是否拥有该控件的创建权
if (ctrl.InvokeRequired)
{
// 步骤2:不在UI线程 → 通过Invoke在UI线程上“重新调用”本方法
ctrl.Invoke(new Action(() => SafeSetText(ctrl, text)));
return;
}
// 步骤3:已在UI线程 → 直接更新控件
ctrl.Text = text;
}
至此,框架文件全部就位。再细心打磨,就成功移植CVI2010+MySQL+CH341DLL到C#+SQLite+CH341DLLA64了。
附件是C#源码: