ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

高级VB实战:SendInput、MSComm、Inet控件与GDI绘图全解析

高级VB实战:SendInput、MSComm、Inet控件与GDI绘图全解析 简介这是一份为具备一定Visual Basic基础、渴望突破初级瓶颈的开发者准备的经典进阶资料以著名VB专家Matthew Curland的《Advanced Visual Basic》为核心系统梳理面向对象编程、事件驱动机制、ADO.NET数据库访问、多线程并发、COM组件自动化、Web服务调用、性能优化与调试测试等高级主题帮助读者从“会写代码”走向“写出高质量、可维护的专业代码”。资源共327个文件压缩包约10.33MB文件类型覆盖81个bas标准模块、34个cls类模块、15个frm窗体、9个ctl用户控件、7个ocx控件、9个odl接口定义以及tlb类型库和exe可执行程序等既有直接可用的源码工程也有编译好的组件适合边学边改、按模块拆解。目前已有354人学习下载。配合随附的PDF电子书与VBoost、ROTHook、DispatchDelegator等大量示例可以深入理解VB内部机制、内存管理与高级调试技巧掌握构建专业Windows应用和可复用ActiveX组件的能力是系统提升VB编程水准的优质实战资源。 说实话做了这么多年Visual Basic相关的开发和培训我越来越觉得“高级VB”这四个字被很多人误解了。很多人以为学会API声明、会用几个高级控件就是高级也有人觉得VB已经过时没必要深究。但真正接手过工业上位机、设备控制、自动化脚本的人都会明白VB在桌面工具链里的地位依然稳得很——它语法亲民IDE上手快和Windows系统层API的亲和力极强在“快速交付一个小工具”这件事上几乎没有对手。这篇文章我想用几个真实能落地的方向来聊“高级VB编程”模拟键盘输入的SendInput、串口通信的MSComm控件、Inet控件下载文件、GDI绘制动态表盘每个都会给出可直接抄走的完整代码并把我平时踩过的坑和排查思路一并写上。内容更适合有基础语法知识、想往“能真正解决生产环境问题”方向走的朋友也适合刚接触老旧VB项目的同学用来补课。1. 高级VB到底“高”在哪先拆解思路1.1 不是语法升级是调用系统能力VB的语法二十年几乎没有大变化你不太可能靠背几个冷门关键字就“高级”起来。真正拉开差距的是你能不能借助VB去调用操作系统或者底层硬件的能力。比如自动录入一个ERP系统你没有接口权限传统做法是让用户一个个填高级做法就是SendInput把键盘事件直接投递到前台窗口模拟真人操作——这在报表录入、批量台账维护场景里极其好用。类似的能力还包括串口通信、网络请求、API调用、GDI绘图、注册表操作、进程管理。本质上VB是壳系统API和组件对象才是内核。理解了这一层“高级VB“学的就不是语法而是一整套和Windows打交道的方法论。1.2 选型考量为什么老项目还用VB很多朋友问我现在Python、C#这么流行何必还守着VB我一般会反问一句你的客户是不是还有一台装着Windows XP的老工控机是不是设备厂商只提供了ActiveX控件是不是需求明天就要、没有时间搭一套复杂框架在这些场景里VB的快速开发优势是实实在在的。我就接手过一个产线项目设备方只给了一个MSComm的串口通讯协议现场PC还是32位Win7要求在三天内出一个扫码比对程序。用VB只需要拖控件、写事件、加判断一天半就完成了。反过来说如果你用C#去调同样的事情也行但部署环境、DLL依赖、驱动兼容那些坑会拖慢你很多精力。选工具不选贵的选省事的这就是VB还活在很多工业现场的原因。1.3 我的学习路径建议如果你是零基础直接看这篇文章建议先掌握这些前置点变量类型、数组、循环、函数封装、常用控件属性事件、错误处理。如果你已经会这些那核心精力放在三件事上一是Win32 API的调用二是ActiveX控件的灵活使用三是代码组织方式——把功能拆成模块、类模块而不是全部堆在Form里。我会在后面几节给出几个完整例子你跟着敲一遍再改成自己的业务需求进步会非常快。2. 核心实战一SendInput模拟键盘输入2.1 为什么需要SendInput而不是KeySendVB自带的SendKeys很简单但稳定性一般尤其遇到管理员权限窗口、某些老系统界面时经常发生按键丢失、无法输入的情况。SendInput是Windows API提供的原生低级键鼠事件接口直接把输入事件注入系统消息队列大部分场景下都能稳定模拟。我记得很早以前用SendKeys给一台老设备的上位机程序填参数CPU稍微忙一点就会少按键客户测试时老觉得不靠谱。换成SendInput之后再也没出现过丢键问题。这两者的差异就好比你叫朋友帮你传话SendKeys和直接走到对方耳边说SendInput——虽然都能到但后者路径更短、不容易被中途打断。2.2 SendInput的完整代码实现在VB里调用SendInput需要先定义结构体和Api声明然后封装一个按键函数。结构体这块不能写错字段偏移只要错一个整个调用就会失败甚至崩溃。我直接给出我常用的完整模块代码Private Type POINTAPI X As Long Y As Long End Type Private Type MOUSEINPUT dx As Long dy As Long mouseData As Long dwFlags As Long time As Long dwExtraInfo As Long End Type Private Type KEYBDINPUT wVk As Integer wScan As Integer dwFlags As Long time As Long dwExtraInfo As Long End Type Private Type HARDWAREINPUT uMsg As Long wParamL As Long wParamH As Long End Type Private Type INPUTUNION mi As MOUSEINPUT ki As KEYBDINPUT hi As HARDWAREINPUT End Type Private Type INPUT_TYPE dwType As Long u As INPUTUNION End Type Private Declare Function SendInput Lib user32.dll _ (ByVal nInputs As Long, pInputs As INPUT_TYPE, _ ByVal cbSize As Long) As Long Private Declare Sub keybd_event Lib user32.dll _ (ByVal bVk As Byte, ByVal bScan As Byte, _ ByVal dwFlags As Long, ByVal dwExtraInfo As Long) Private Const INPUT_KEYBOARD 1 Private Const KEYEVENTF_KEYUP H2 Public Sub PressKey(ByVal vKeyCode As Integer) Dim inputEvent As INPUT_TYPE inputEvent.dwType INPUT_KEYBOARD inputEvent.u.ki.wVk vKeyCode inputEvent.u.ki.dwFlags 0 SendInput 1, inputEvent, Len(inputEvent) inputEvent.u.ki.dwFlags KEYEVENTF_KEYUP SendInput 1, inputEvent, Len(inputEvent) End Sub这段代码定义了一个模拟“按下再松开”的按键流程核心是SendInput这个API。dwType表示事件类型1代表键盘事件wVk是虚拟键码比如回车是13Tab是9。需要注意Len(inputEvent)的用法它告诉API结构体有多大如果传了错误的字节数函数会静默失败到时候你找半天都找不到原因。2.3 实际使用批量台账录入脚本我在这里说一个做过的实际用法一个仓库系统每天要把Excel里的几百条编号录入到老旧的Web界面里。系统不提供导入接口人工录入又累又容易错。我是这么设计的程序用Excel对象模型逐行读取编号找到界面的输入框坐标后点击激活然后调用PressKey发送数字和回车。每个编号间隔0.3秒跑完整个Excel自动换下一份。核心代码如下主要是循环发送Dim i As Integer For i 1 To 500 假设当前输入框已经聚焦 SendText SN Format(i, 0000) PressKey vbKeyReturn 等待界面保存 Sleep 300 激活下一行输入框 SendKeys {TAB} Next iSendText是我封装的一个字符串输出函数本质是逐个字符查虚拟键码再调PressKey。还有一个经验任何自动化脚本都必须有延时控制。界面响应速度有波动延时太短会丢输入太长则效率太低。我的建议是先用一个较小的延时跑三轮测试找到稳定工作的临界值再适当加50毫秒左右的余量。2.4 注意UAC权限与焦点窗口SendInput最怕两件事一是前台窗口不是目标窗口。你必须确保执行脚本之前目标窗口已经激活并且输入框已经获得焦点。如果程序被切到后台按键会打到别的窗口上轻则输入错误重则误操作。我习惯在执行前加一个延迟给操作人员3秒时间切到目标窗口再把窗体和按钮的Enabled设为False防止误点。第二件事是UAC权限。如果目标程序是以管理员权限运行的而你的VB程序不是那么SendInput会被直接屏蔽按键一个都进不去。解决办法是让VB程序也以管理员权限启动或者用任务计划程序以最高权限运行。这个坑很隐蔽不提前排查很容易怀疑是代码问题。3. 核心实战二MSComm串口通信3.1 项目场景扫码枪数据采集工业现场的扫码枪、电子秤、PLC通常走串口通信。MSComm是VB经典串口控件虽然老但稳定配合事件驱动机制非常适合上位机数据采集。我做过一个小整机设备的项目产线工人扫产品条码数据实时进入数据库比对不合格品立刻亮红灯报警。核心就是MSComm接收扫码枪的ASCII字符串然后解析条码业务逻辑。硬件连接其实很省心一个USB转串口线接上扫码枪设备管理器里找到COM口号代码里配置好串口参数就行。真正的技术点在于怎么可靠地接收和解析不定长的数据包。3.2 MSComm常用属性配置一定要先根据设备手册确认波特率、数据位、停止位、校验位。扫码枪大多是9600,8,N,1某些老PLC可能用19200或4800。接错参数不会报错只会出现乱码或者收不到数据这也是排查时要优先排查的。With MSComm1 .CommPort 3 串口号从1开始计数 .Settings 9600,N,8,1 .InputMode comInputModeText .RThreshold 1 每收到一个字节就触发OnComm .SThreshold 0 .InputLen 0 .PortOpen True End WithRThreshold 1是最常用也最灵敏的配置能保证每个到达的字节都会触发OnComm事件。InputLen必须设为0表示一次性读取接收缓冲区的全部内容。InputMode按需设置为文本或二进制文本模式适合扫码枪这类ASCII设备二进制模式适合和PLC交换协议帧。3.3 接收事件与数据解析OnComm事件是核心入口我习惯在里面先判断CommEvent的属性值再把接收到的字符拼接进模块级的接收变量里最后按结束符判断一条完整的数据。这样做能有效处理数据分包的问题因为串口数据不一定会一次性到达可能分好几次事件才能收到一条完整指令。Private Sub MSComm1_OnComm() Dim strData As String Select Case MSComm1.CommEvent Case comEvReceive strData MSComm1.Input If strData Then buffer buffer strData If InStr(buffer, vbCrLf) 0 Then Call ProcessLine(buffer) buffer End If End If End Select End SubProcessLine就是你的业务解析函数比如取出条形码、查询数据库、控制指示灯。这样设计的好处是主逻辑和通信逻辑完全分离模块清晰后续加新设备只要换解析函数就行。我做项目时还会另加一个串口长时间无数据的看门狗定时器。设备偶尔会掉线如果上位机没有反应操作员根本不知道。每10秒检查一下最近收到数据的时间如果超时界面提示并报警这样问题能被现场人员第一时间发现。3.4 常见坑串口占用与32位依赖MSComm控件是ActiveX组件有些精简版系统可能没有注册。放到新机器上运行时会报“不能加载控件”的错误。解决办法是拷贝MSComm32.ocx到系统目录并执行regsvr32 MSComm32.ocx。这个组件是32位的VB6编译出的程序也是32位在现代64位Windows上需要以x86模式注册一般不会出大问题。串口占用也是个高频问题。设备管理器里看到COM3被占用但你代码里写的是COM3打开端口就会报“端口无法打开”。先确认没有串口调试助手或者其他软件占住端口再检查USB转串口的驱动装没装好。我曾经在客户现场排查了半小时最后发现我自己的调试助手没关占了串口——这件事后来被我写进项目交付文档提醒所有调试人员。4. 核心实战三Inet控件下载文件与FTP操作4.1 为什么不用URLDownloadToFileVB里下载文件有好几种方式WinINet的InternetOpenUrl、URLDownloadToFile API、还有Inet控件。我推荐Inet控件的主要原因是事件驱动、可控性强你能随时感知到下载状态超时也能控制。URLDownloadToFile虽然代码短但它是同步阻塞的遇到网络慢或服务器无响应程序就卡死了。Inet控件的底层是WinINet支持HTTP和FTP可以满足大部分桌面工具的需求。我曾在多台工控机上用它定时下载设备配置参数文件从服务器拉取最新版本然后本地覆盖稳定运行了很久。4.2 Inet下载文件完整示例先把Inet控件拖到窗体上不需要放在可见区域位置无所谓。关键代码主要是两个发请求然后在StateChanged事件里判断状态并读取数据流。这里我直接把下载的每个字节写入文件避免了内存占用过高的问题特别适合下载大文件。Private Sub Command1_Click() Inet1.URL http://192.168.1.100/version/config.ini Inet1.AccessType icUseProxy Inet1.Protocol icHTTP Inet1.Execute , GET, , Pragma: no-cache vbCrLf End Sub Private Sub Inet1_StateChanged(ByVal State As Integer) Dim bytData() As Byte Select Case State Case 12 icResponseCompleted 响应完成 bytData Inet1.GetChunk(1024, icByteArray) Open App.Path \config.ini For Binary As #1 Do While UBound(bytData) 0 Put #1, , bytData bytData Inet1.GetChunk(1024, icByteArray) Loop Close #1 MsgBox 下载完成 End Select End SubState值为12代表响应完成这时候所有内容都到了缓冲区可以用GetChunk按块读取。1024的块大小是我常用的兼顾了速度与内存占用。Binary模式写入文件是通用做法不管是文本还是二进制文件都能正确落地。要注意的是GetChunk在事件里可以被调用一次或多次只要返回的数组长度大于0就说明还有数据继续读取直到数组为空。4.3 FTP上传与目录操作除了下载Inet控件还能做FTP上传、创建目录、改名等操作。我的一个日常运维工具就是用Inet自动把生成的报表上传到局域网FTP服务器省去人工打开FTP客户端再拖文件的操作。执行方式和下载类似只要改变Execute的参数结构Inet1.Protocol icFTP Inet1.URL ftp://192.168.1.50/reports/ Inet1.Execute , PUT C:\report.txt report.txtFTP命令字符串遵循标准FTP指令。PUT后面跟本地路径和远端文件名就可以完成上传。这个控件使用的是连接池机制只要不调用Cancel连接可以复用连续上传多个文件速度也不错。为了安全起见项目中不要把密码明文写在代码里我一般放在一个单独的配置文件启动时读取并动态赋值给UserName和Password属性。4.4 网络异常的容错处理网络程序最忌讳静默失败。Servers无响应时StateChanged里可能一直等不到12状态。我习惯再加一个Timeout属性设置以及一个定时器做超时兜底。比如30秒还没有完成就提示用户“网络连接超时请检查服务器地址和网络状态”。同时把每次下载的日志写到本地文件方便后续排查。另一个经验是在线更新配置时先下载到临时文件校验完整后再覆盖正式文件避免下载到一半程序突然退出导致配置文件损坏。系统运行越久这种细节就越显得重要因为它们直接关系到软件的健壮性。5. 进阶实战四GDI绘图实现动态表盘5.1 用VB写一个罗盘时钟的思路动态表盘、罗盘时钟这类可视化组件很适合用来展示GDI绘图的进阶技术。如果你接到类似需求比如给车间的LED看板做一个实时时钟界面或者给设备做一个液位仪表盘GDI就是最省事的方案。我演示一个罗盘时钟——外层表盘、刻度线、时针分针秒针全部用VB的绘图指令实时绘制。这种程序的难点不是循环画线而是坐标计算。表盘上的每个点(x, y)坐标都依赖圆心坐标和半径以及对应角度。换算公式很简单x cx r * sin(angle)y cy - r * cos(angle)角度要用弧度制。表盘是360度小时刻度是30度一格分钟刻度是6度一格。搞懂这个换算逻辑绘制的其他复杂图案基本都是套公式。5.2 核心绘制代码窗体上放一个TimerInterval设为1000毫秒每秒触发一次重绘。为了简单起见我直接在Form_Paint事件里画Timer触发时调用Form1.Refresh。代码分为画表盘、画刻度、画指针三部分Private Sub DrawClock() Dim cx As Long, cy As Long, r As Long Dim i As Integer Dim angle As Double, rad As Double Dim x1 As Long, y1 As Long, x2 As Long, y2 As Long cx Me.ScaleWidth / 2 cy Me.ScaleHeight / 2 r IIf(cx cy, cx, cy) - 30 画外圈表盘 Me.Circle (cx, cy), r, vbBlue Me.FillStyle 0 Me.FillColor vbBlack Me.Circle (cx, cy), r - 8, vbCyan 画60个分刻度其中大刻度代表小时 For i 0 To 59 angle i * 6 每个刻度6度 rad angle * 3.14159265 / 180 If i Mod 5 0 Then x1 cx (r - 18) * Sin(rad) y1 cy - (r - 18) * Cos(rad) x2 cx (r - 5) * Sin(rad) y2 cy - (r - 5) * Cos(rad) Me.Line (x1, y1)-(x2, y2), vbWhite Else x1 cx (r - 12) * Sin(rad) y1 cy - (r - 12) * Cos(rad) x2 cx (r - 5) * Sin(rad) y2 cy - (r - 5) * Cos(rad) Me.Line (x1, y1)-(x2, y2), vbGray End If Next i End Sub这里使用的是ScaleWidth和ScaleHeight而不是窗体宽高因为ScaleMode不同时实际绘图坐标会不一样。给刻度分粗细、分颜色的做法在视觉上能模拟真实表盘的质感同时也让用户更快识别时间这是一个很细节但很加分的做法。5.3 指针绘制与防闪烁技巧指针的典型画法是Line方法从圆心到指向角度画线线宽用DrawWidth属性控制。秒针最细分针中粗时针最粗。为了让表盘看起来立体可以给指针层叠一个不同宽度和亮度的线条。绘制顺序也有讲究先画表盘再画刻度再画指针最后画圆心盖帽这样指针根部和圆心连接处才整体。一个避不开的问题是闪烁。Timer每秒触发一次Refresh整个窗体重绘如果绘图量很大或者系统性能一般指针运动时有明显的闪动。解决办法是双缓冲创建一个内存中的PictureBox或者使用BitBlt将完整图像一次性拷贝到屏幕。原理是把所有绘图先画到内存画布再将整块区域一次性刷新到界面避免逐条绘制导致闪烁。代码上相当于在内存APICreateCompatibleDC里操作完毕后再Paint到窗体VB里最简单的方式是先放在一个临时PictureBox上再PaintPicture复制过来。这套GDI技巧不止用于时钟还常用于仪表盘、示波器波形、趋势图、模拟监控大屏等。把坐标换算和双缓冲两个基础吃透做可视化组件基本不需要依赖第三方控件。6. 常见问题与排查技巧实录6.1 高频问题速查表我在做VB项目时遇到问题最多的几个点整理成一个表方便大家直接排查现象可能原因排查与处理方法SendInput没反应窗口未聚焦或权限不足检查目标窗口是否前台确认程序是否以管理员身份运行串口收不到数据串口号错误或波特率不匹配检查设备管理器确认硬件参数关闭其他占用串口的软件串口数据乱码数据位/停止位/校验位不匹配对照设备手册重新设置Settings字符串Inet下载卡死网络异常或服务器无响应设置Timeout属性加看门狗定时器做超时判断GDI闪烁严重没有双缓冲用内存PictureBox或BitBlt先渲染再整体刷新提示缺少MSComm控件目标机器未注册OCX使用regsvr32注册MSComm32.ocx6.2 一个隐蔽问题API声明字节对齐很多初学API的朋友会遇到一个诡异问题代码完全照着例子写SendInput却返回0GetLastError返回87参数错误。绝大多数原因是结构体对齐字段不匹配。VB6的默认对齐方式和32位C语言结构体不完全一致需要在结构体定义前面加上Option Explicit并且在模块顶部加入对齐指令。如果依然不行可以直接用我上文的代码那些字段类型都是实测过的。6.3 越用越顺手的几个习惯我再分享几个从实战中养成的习惯看上去很小但对项目维护帮助极大。第一个是每个模块开启Option Explicit强制变量先声明再使用。没有这一条累到深夜时一个拼写错变量就能让你排查半小时。第二个是统一封装API调用把SendInput、注册表操作、文件操作都做成独立模块不掺杂在窗体事件里。第三个是所有外部资源连接都写好关闭逻辑——串口关闭、文件关闭、Inet取消防止用户连续操作时资源泄漏。最后还有一个习惯不一定和技术相关但对项目交付很关键程序里留下一份简单的“使用与排障说明”弹窗把常见的“串口被占”“提示缺少控件”这类问题直接告知使用人怎么处理。一线操作员不是开发者能自己解决的问题不需要打电话找你。这其实也是对项目负责的做法我在交付核心设备程序时都会加上这样一个说明页。6.4 用版本管理思维维护老代码老项目的代码容易越改越乱特别是没有版本管理习惯的VB项目。我建议哪怕一个人开发也把工程目录纳入一个简单的版本管理工具里哪怕只是每次修改前手动复制一份带日期的备份。API声明的调整、控件版本的升级、系统环境变化都可能导致以前能跑的代码突然罢工有了版本记录才能快速回滚和对比差异。如果你维护的设备程序涉及多个客户现场强烈建议在代码里加一个版本号和编译时间的自动输出。当现场人员报问题时你拿到版本号就能直接判断是哪一版代码不用猜来猜去。这在工控领域等于给自己的售后服务加了一条快车道。最后的体会做VB开发这十几年我最大的体会是高级从不在语言本身的复杂程度而在于你用它解决了多少真实的问题。SendInput帮用户免除了大批量录入的重复劳动MSComm让一台设备连上了数字化的数据流Inet把运维从手动下载中解放出来GDI让老掉牙的界面也能呈现出现代化的视觉效果——每一件事背后都是VB这个“老工具”在几十年的Windows生态里积累下来的扎实功底。现在编程世界的风向标转得很快但工业现场、企业内网、老旧系统维护这些角落VB依然安静地跑着。如果你想接触这类实战场景不妨从我给出的这几个方向入手照着代码敲一遍再改造成自己的业务逻辑。过程中遇到卡壳回到这篇文章的排查表里翻一翻多数问题都有答案。等你亲手完成一个“能帮别人省时间”的VB程序你一定会感受到这门老语言的生命力远比传闻中顽强。本文还有配套的精品资源点击获取
返回列表