简介:《Advanced Visual Basic(高级VB编程)》是一套由 VB 专家 Matthew Curland 编写的高阶学习资料包,内容覆盖面向对象类设计、事件处理与异常捕获、多线程调度、ADO.NET 数据库访问、COM 自动化、网络与 XML/Web 服务、性能优化,还涉及反射、泛型、委托、Lambda 表达式等进阶特性,适合已有 VB 语法基础、希望向中高级开发者进阶的程序员。压缩包共 327 个文件,大小约 10.33MB,类型非常齐全:81 个 bas 公共模块提供通用函数,34 个 cls 类模块构建业务对象,33 个 vbp 工程文件组织项目,15 个 frm 窗体承载界面;另含大量 odl、ctl、ocx 控件与类型库资源,以及 h 头文件、rc 脚本、tlb 类型库和 8 个 exe 演示程序,并附 PDF 电子书,便于对照编译与理解。目前已有 354 人学习下载。通过这套资料,读者能深入掌握高级 VB 的底层运行机制,获得可直接编译的完整工程、可复用的类与控件实现,还能借鉴作者在委托、反射、线程泵、内存分配、异常处理等方面的封装思路与排错经验,尤其适合在 Windows 平台从事 VB 开发或维护老项目的技术人员作为案头参考。 VB这门语言,聊起来总有人要问一句:这年头还有人用?说实话,真正做工业自动化、维护老旧系统、写桌面办公辅助工具的人,听到这个问题只能笑笑。我手头到现在还维护着七八套VB6写的老系统,跑在客户的工控机和Windows XP老机器上,一跑就是十几年没动过。所谓“高级VB编程”,听起来像个老古董词汇,但它真正的价值恰恰在于:当别人都追新框架、新语言的时候,你能用最少的依赖、最直接的方式,把一件具体的事情干成。
这篇文章不聊控件摆放,也不讲For循环怎么写,专门说那些让VB真正“高级”起来的东西:调用Windows API、用SendInput模拟键盘输入、用MSComm控件啃串口、用Inet控件下载文件,再到WinCC画面跳转这类工业工程集成实战。每一段我都会给可复制的代码,再讲清楚背后的原理和踩过的坑。无论你是被迫接手老系统,还是在工控、自动化行业里写配套工具,这份内容都值得存一份。
1. 高级VB编程到底“高级”在哪
1.1 VB没死,只是换了阵地
很多人对VB的印象停留在“拖几个控件、写两行事件”的入门阶段,这确实是VB最友好的一面。但真正让VB能在生产环境里活二十年的,是它和Windows底层、各种硬件设备、工业组态软件的深度绑定。VB6编译出来的程序不依赖运行时环境,拷贝个exe就能跑,这一点在工控机上特别值钱——客户现场没有网、没有权限装新环境,你拿VB写个小工具,U盘拷过去双击就能用,别的语言还真不一定做得到。
同时,VB6的ActiveX控件体系虽然老,但生态积累极其深厚。MSComm串口控件、Inet网络控件、Winsock控件、MSFlexGrid表格控件,都是当年微软官方出品,稳定性和文档完整度比现在很多开源库都好。高级VB编程的核心,不是语法技巧,而是知道该找哪个控件、哪个API,以及怎么把它们组合起来解决实际问题。
1.2 三个真正的进阶方向
我自己把高级VB编程分成三条线,覆盖了绝大多数实际需求:
- 系统级操作:通过Declare声明调用Windows API,比如模拟键盘鼠标、读写注册表、操作窗口句柄、枚举进程。这类操作突破了VB自身的能力边界,是“高级感”最直接的来源。
- 控件深度应用:不满足于MSComm通信、Inet下载这类基本用法,而是理解控件的事件机制、异步模型和状态机,写出可靠稳定的代码。
- 跨系统集成:VB程序很少孤立运行,要跟数据库、Excel、PLC、组态软件(比如WinCC)对话。这部分考验的是对接口协议和数据格式的理解。
这三条线,就是这篇文章的主干。
1.3 选型思考:什么时候该选VB
我并不是说VB万能。新项目让我选,我也会优先考虑C#、Python。但在这些场景下,VB依然是合理甚至最优的选择:老系统的增量维护、工控机上快速写辅助工具、需要和WinCC/Siemens组态深度集成的项目、以及团队成员只会VB的环境。
说白了,高级VB编程是一种“边界能力”,它的意义不在于炫技,而在于让你在特定场景下不掉链子。掌握了这些,再老的语言也能成为你的底牌。
2. 系统级操作:API声明与SendInput模拟键盘输入
2.1 从声明开始,VB如何触碰Windows底层
VB6本身能做的系统级操作非常有限,但它提供了一个后门:Declare声明。通过Declare可以调用user32.dll、kernel32.dll里的标准API函数,相当于直接跟Windows内核打交道。比如下面这句:
Private Declare Function keybd_event Lib "user32" _ (ByVal bVk As Byte, _ ByVal bScan As Byte, _ ByVal dwFlags As Long, _ ByVal dwExtraInfo As Long) As Long声明之后,VB里就能像调用普通函数一样调用keybd_event。关键点在于参数类型必须严格对应API定义,Byte对应BYTE,Long对应LONG,字符串如果是ByVal String则对应LPCTSTR。这里最容易出问题的是32位和64位差异,VB6是纯32位程序,在64位系统上通过SysWOW64兼容层运行,绝大多数user32 API都能正常调用,但如果声明了错误的参数类型,轻则返回错误值,重则直接导致程序崩溃。
2.2 用SendInput模拟键盘输入的完整代码
keybd_event虽然简单,但属于旧式模拟方案,Windows官方推荐的是SendInput,它支持输入事件合并、更精确的时间戳,也更容易被目标程序识别。在VB6里,SendInput的结构体声明稍微繁琐,因为VB6不支持C语言的Union(共用体),所以常用的做法是退一步,用keybd_event搞定大多数需求,核心代码如下:
Private Declare Function keybd_event Lib "user32" _ (ByVal bVk As Byte, _ ByVal bScan As Byte, _ ByVal dwFlags As Long, _ ByVal dwExtraInfo As Long) As Long Private Const KEYEVENTF_KEYUP = &H2 Private Const VK_CONTROL = &H11 Private Const VK_MENU = &H12 ' Alt键 Private Const VK_S = &H53 Private Sub SendCtrlS() ' 模拟 Ctrl + S keybd_event VK_CONTROL, 0, 0, 0 keybd_event VK_S, 0, 0, 0 keybd_event VK_S, 0, KEYEVENTF_KEYUP, 0 keybd_event VK_CONTROL, 0, KEYEVENTF_KEYUP, 0 End Sub Private Sub SendAltTab() ' 模拟 Alt + Tab 切换窗口 keybd_event VK_MENU, 0, 0, 0 keybd_event VK_TAB, 0, 0, 0 keybd_event VK_TAB, 0, KEYEVENTF_KEYUP, 0 keybd_event VK_MENU, 0, KEYEVENTF_KEYUP, 0 End Sub这套代码的取值来自Windows SDK的虚拟键码表,VK_CONTROL是0x11,VK_MENU是0x12,VK_S是0x53。按键按下和抬起必须成对发送,否则目标程序会认为按键一直被按住,可能引发连带操作。实际项目里我用这套代码做收银系统的自动对账、连续录入单据,稳定性很高。
2.3 API调用最容易踩的坑
- 参数类型错误:这是最大的坑。VB里Integer是16位,C语言的INT是32位,如果API声明用错类型,传进去的数据就是错乱的。拿不准的时候,把API声明和MSDN文档对照着看。
- ByVal和ByRef混用:API声明里没有明确说明时,基本都是ByVal传值。把ByVal漏掉,API就读到指针地址的值,轻则返回垃圾数据,重则蓝屏死机。
- 32位兼容性:现在新出的电脑基本64位,VB6程序跑在WOW64层,大部分API没问题,但个别涉及驱动、内存直接访问的API会受限。遇到诡异的崩溃,先怀疑API兼容性。
注意:模拟按键类功能只应用在你自己有权限操作的系统和软件上,不要拿去做绕过验证之类的操作,合规永远是第一位。
3. 串口通信实战:MSComm控件收发数据
3.1 MSComm控件的核心属性与事件
串口通信是工控领域绕不开的硬需求。VB6时代最成熟的方案就是微软的MSComm控件(Microsoft Comm Control 6.0)。它的使用思路非常简洁:设置端口号、波特率、数据位、校验位、停止位,打开端口,然后事件驱动式收数据。核心属性如下:
- CommPort:端口号,1对应COM1,2对应COM2。如果超过9,要用"\\.\COM10"这类写法,否则会打开失败。
- Settings:通信参数,格式是“波特率,校验位,数据位,停止位”,比如"9600,N,8,1"代表9600波特率、无校验、8位数据、1位停止位。
- InputMode:接收数据模式,comInputModeText是文本模式,comInputModeBinary是二进制模式。读取设备返回的16进制数据时,必须用二进制模式。
- RThreshold:接收缓冲区里有多少字节时触发OnComm事件。设为1表示收到1个字节就触发,设为0则关闭事件。
- PortOpen:打开或关闭串口。
3.2 一个完整的串口收发示例
下面是一个我最常用的串口收发模板,它做的事情是:打开COM1,发送一个“AT”指令,然后接收设备返回的所有文本并显示在文本框里。这块代码我在多个条码秤、工业仪表项目中直接套用过。
Private Sub Form_Load() ' 初始化串口 MSComm1.CommPort = 1 MSComm1.Settings = "9600,N,8,1" MSComm1.InputLen = 0 ' 读取接收缓冲区全部内容 MSComm1.RThreshold = 1 ' 接收缓冲区有1个字节就触发事件 On Error Resume Next MSComm1.PortOpen = True If Err.Number <> 0 Then MsgBox "串口打开失败,请检查端口是否被占用。", vbCritical Exit Sub End If On Error GoTo 0 MSComm1.Output = "AT" & vbCr ' 发送AT指令,按设备协议加回车 End Sub Private Sub MSComm1_OnComm() ' 串口事件处理 Dim strData As String Select Case MSComm1.CommEvent Case 2 ' comEvReceive 接收事件 strData = MSComm1.Input Text1.Text = Text1.Text & strData End Select End Sub Private Sub Form_Unload(Cancel As Integer) ' 退出时一定要关串口,否则其他程序无法使用 If MSComm1.PortOpen Then MSComm1.PortOpen = False End If End Sub这里有个细节:InputLen设为0才能一次性把缓冲区数据全部读出,否则只会读回InputLen指定长度的字节。RThreshold设为1是“收到一个字节就通知”,对速度要求不高的文本协议足够用。如果对接的仪表一次返回几十上百个字节,而你的程序处理不过来,可以适当把RThreshold调大,减少事件触发频率。
3.3 串口调试的实操心得
MSComm最让人头疼的是没有调试信息,串口打不开、收不到数据,很难直接定位。我的经验是:
- 先用串口调试助手确认设备本身有输出,排除设备端问题。
- 再确认端口号是否被占用,VB6的MSComm对端口占用报错处理不够友好,建议加On Error保护。
- 处理二进制数据时,直接把收到的字节转成十六进制字符串显示,避免乱码问题。
十六进制转换函数也一并贴出来:
Private Function ByteToHex(bytes() As Byte) As String Dim i As Long Dim s As String For i = LBound(bytes) To UBound(bytes) s = s & Right("0" & Hex(bytes(i)), 2) & " " Next ByteToHex = Trim(s) End Function这套组合基本能解决90%的串口调试问题。剩下的10%,往往是设备端协议没对齐,比如波特率不一致、帧头帧尾解析错误,那就要拿着设备手册逐字节对了。
4. 网络请求与文件下载:Inet控件和替代方案
4.1 Inet控件的典型下载流程
VB6原生自带的Inet控件(Microsoft Internet Transfer Control)封装了HTTP、FTP等协议,可以下载文件、提交表单。它最大的特点是异步工作:调用Execute后,程序不阻塞,等服务器响应完成后触发StateChanged事件。对于下载文件这种耗时操作,比直接用WinInet API方便很多。
常见的下载流程是:设置URL和协议,然后调用Execute发起请求,在StateChanged事件里监听状态码12(icResponseCompleted),表示响应完成。此时再用GetChunk分批读取数据,避免一次性加载大文件导致内存暴涨。
4.2 下载文件的完整代码
Private Const icResponseCompleted = 12 Private Const icByteArray = 1 Private Sub Command1_Click() Inet1.Protocol = icHTTP ' 使用HTTP协议 Inet1.URL = "http://example.com/files/setup.zip" Inet1.Execute , "GET" End Sub Private Sub Inet1_StateChanged(ByVal State As Integer) Dim bData() As Byte Dim fNum As Integer If State = icResponseCompleted Then ' 以二进制方式打开本地文件准备写入 fNum = FreeFile Open "D:\download\setup.zip" For Binary Access Write As #fNum ' 分批获取数据,每批64KB Dim chunkSize As Long chunkSize = 65536 Do While True bData = Inet1.GetChunk(chunkSize, icByteArray) If UBound(bData) < 0 Then Exit Do ' 没有数据时数组上界为-1 Put #fNum, , bData DoEvents ' 保持界面响应 Loop Close #fNum MsgBox "下载完成" End If End Sub不同版本的VB对GetChunk返回值处理略不同,建议循环判断数组长度,而不是依赖返回的字节数。下载过程中DoEvents很重要,否则界面会假死。而且Inet控件对重定向支持一般,如果URL经历了302跳转,可能要动态更新URL再执行一次。
4.3 比Inet更省心的替代方案
Inet控件的缺点是错误处理弱、超时设置不灵活、有些HTTPS站点(尤其是新版TLS)会握手失败。其实VB6程序下载文件,还有一个更简单的办法:用URLDownloadToFile这个API。它不需要控件,一个函数直接搞定:
Private Declare Function URLDownloadToFile Lib "urlmon" _ Alias "URLDownloadToFileA" _ (ByVal pCaller As Long, _ ByVal szURL As String, _ ByVal szFileName As String, _ ByVal dwReserved As Long, _ ByVal lpfnCB As Long) As Long Private Sub DownloadFile() Dim lResult As Long lResult = URLDownloadToFile(0, _ "http://example.com/setup.zip", _ "D:\download\setup.zip", 0, 0) If lResult = 0 Then MsgBox "下载成功" Else MsgBox "下载失败,错误码:" & Hex(lResult) End If End Sub这个API是同步调用,适合文件不是特别大的场景。它内部会处理TLS握手,只要系统能正常用IE打开这个链接,它基本就能下载。我做升级包下载工具时优先用这个方案,代码量少、稳定、好排查。
5. 工程集成场景:WinCC画面跳转与Office联动
5.1 WinCC里用VB脚本实现按钮跳转
西门子WinCC组态软件虽然是工业级SCADA平台,但它的脚本引擎大量使用VBScript(VB语系),所以“VB编程”这个词在工控语境下还会延伸到WinCC脚本里。最典型的场景就是单击按钮跳转到另一幅画面。
在WinCC图形编辑器中,给按钮的“单击鼠标”事件添加VBS脚本,核心就一句话——修改画面窗口的PictureName属性:
Sub OnClick(ByVal Item) ' 跳转到主画面 Set objScreen = HMIRuntime.ActiveScreen objScreen.ScreenItems("画面窗口1").PictureName = "主画面.PDL" End Sub如果当前画面上的画面窗口名字叫“画面窗口1”,运行到这一句就会加载“主画面.PDL”。这里的要点是:必须先通过HMIRuntime.ActiveScreen拿到当前画面对象,再通过ScreenItems集合定位画面窗口控件。直接写ScreenItems会找不到对象,因为脚本上下文还没有关联到当前画面。
还有一种方式是用OpenPicture函数,在WinCC的全局脚本里比较常用:
Sub OnClick(ByVal Item) HMIRuntime.OpenPicture "主画面.PDL", 0, 0, 850, 550, "", "", 1, 1 End SubOpenPicture支持指定窗口位置和尺寸,适合弹出独立窗口的画面跳转。两个方案的区别在于:改PictureName是在现有画面窗口内切换,界面框架不变;OpenPicture是打开新的画面窗口。按项目需要选择,没有绝对的对错。
5.2 VB操作Excel生成报表
除了WinCC,VB在办公自动化领域的集成能力也很强。用VB操作Excel生成报表是老传统了,核心思路是通过CreateObject创建Excel.Application对象,然后填充数据、保存文件。一个精简示例:
Dim xlApp As Object Dim xlBook As Object Dim xlSheet As Object Set xlApp = CreateObject("Excel.Application") xlApp.Visible = False ' 后台运行,不显示界面 Set xlBook = xlApp.Workbooks.Add Set xlSheet = xlBook.Worksheets(1) xlSheet.Cells(1, 1) = "编号" xlSheet.Cells(1, 2) = "数值" xlSheet.Cells(2, 1) = "001" xlSheet.Cells(2, 2) = 100 xlBook.SaveAs "D:\report.xlsx", 51 ' 51表示xlsx格式 xlBook.Close xlApp.Quit Set xlSheet = Nothing Set xlBook = Nothing Set xlApp = Nothing这里的重点是创建对象后的释放顺序:先关工作簿、再退出应用、最后置Nothing。不按这个顺序,容易导致Excel进程残留在后台,占着文件不释放。另外,SaveAs第二个参数51是Open XML格式的枚举值,不同格式对应不同数字,记不住就直接用xlWorkbookDefault常量(在工程中引用Excel对象库后可用)。
5.3 混合场景下的VB代码组织建议
在WinCC、Excel、串口、数据库这些场景混在一起时,VB项目的代码组织直接决定后期维护成本。我的建议:
- 把API声明集中在单独的标准模块里,不要散落在各个窗体中,便于统一排查类型错误。
- 串口、网络、文件下载这类通用功能封装成类模块,向外暴露简单的方法,窗体只管调用。
- 硬编码的路径、端口号、URL统一放配置文件或用Const常量,避免项目交接时到处找参数。
- 关键操作加上错误处理,VB6的On Error Resume Next不是不能用,但要明确知道出错后怎么恢复。
有了这套组织方式,哪怕代码是“古法编程”,结构依然是现代工程化的,接手的人不会想骂你。
6. 常见问题与排查技巧实录
6.1 高频报错与处理方案速查表
| 报错信息或现象 | 常见原因 | 解决办法 |
|---|---|---|
| 编译错误:Declare语句中不允许使用As Any | VB6不支持As Any,多见于64位API声明示例 | 去掉As Any,改用具体类型如ByVal hProcess As Long |
| 运行时错误"5":无效的过程调用或参数 | 串口被占用、API传入空指针 | 检查端口占用,给API参数加空值判断 |
| Inet下载无响应 | URL不可达、协议选择错误、服务器需要HTTPS但用了HTTP | 改为HTTPS,用URLDownloadToFile验证链接可达性 |
| WinCC脚本报“未找到对象” | 画面窗口名写错,或脚本里没有先取ActiveScreen | 确认ScreenItems集合中的名称,逐级引用对象 |
| 程序在64位系统上崩溃 | 某API声明结构不对,或驱动级API被拒绝 | 换成官方标准声明,优先用user32、kernel32公开API |
| 串口收不到数据 | 波特率不匹配、InputMode错误、RThreshold为0 | 用串口助手确认设备参数,检查事件触发条件 |
6.2 调试与定位经验
VB6的调试工具比现代IDE弱不少,但有几个技巧很实用:
- API调用看不清返回值时,先用MsgBox弹出Err.LastDllError,这个属性会告诉你API最后返回的系统错误码,再查系统错误码表定位问题。
- 串口收不到数据,不要盯着代码看,先用串口调试助手确认设备在发数据,再反查VB程序端口号和参数。
- Inet控件状态每次变化都会触发StateChanged,建议用Debug.Print把这些状态码打印到立即窗口,跑一遍就能看到请求卡在哪一步。
- 涉及Excel、数据库等外部组件时,打开任务管理器观察进程是否残留。一旦发现Excel进程没退出,优先检查释放顺序。
这套排查逻辑,不管VB项目多老多乱,都能帮你在最短时间内缩小怀疑范围。
最后再分享一个我多年形成的习惯:VB项目一定要建版本备份目录,每次改动之前把能跑的版本复制一份。因为在工控现场调试的时候,改崩了想回退,没有备用的旧版本真的是叫天天不应。高级VB编程走到最后,拼的不是语法多花哨,而是你踩过的坑够不够多、预案够不够全。希望这篇文章能让你少踩几个我已经趟平的坑。
本文还有配套的精品资源,点击获取