
简介SCADA数据采集与监控系统源码包基于VB与VC混合编程实现面向电力、石油、化工等工业自动化领域的开发者解决现场设备数据采集、实时监控与界面呈现问题。压缩包共84个文件约1019KB包含VC编写的头文件与源文件h/cpp、VB窗体文件frm、DLL动态库、数据库文件及大量界面图标gif等底层通信与数据处理采用VC实现界面层借助VB构建出直观易操作的人机交互面板。系统支持与PLC、RTU等设备通过串口或网络交换数据涵盖实时数据读取、状态监控、报警提示和设备控制等功能。已有180人学习下载适合正在学习工业组态软件或混合语言开发的读者。包内附有完整源代码、项目文件和说明文档可直接对照分析跨语言协作的项目组织方式与SCADA系统模块划分思路。1. SCADA人机界面与数据采集一套能跑通的VBVC双语言工程做工业监控绕不开SCADASupervisory Control and Data Acquisition做SCADA又绕不开历史遗留的VB、VC代码。这套压缩包不是教程文档而是一份真实的、用Visual Basic 6.0和Visual C 6.0混合编程写出来的集群设备监控系统源码包含VB窗体、VC通信组件、设备配置INI、Access数据库和抓包日志。如果你正在维护老旧的监控上位机或者老板让你把一套十年以上的设备监控系统改到能用这份资源值得你从头到尾拆一遍。压缩包里既有MSWINSCK.OCX、MSCAL.OCX这类控件依赖也有ComDll.dll通信组件的完整VC工程还带设备台账和配置界面覆盖了数据采集、界面绑定、报警、存储四个SCADA核心环节很适合做二次开发和改造参考。2. 双语言架构VB6.0界面层与VC6.0通信层如何分工2.1 为什么不是纯VB也不是纯VC早年间做监控系统的常见组合有两种纯VB配MSComm控件做串口或者纯VC做全链路。前者开发快但实时数据处理能力弱后者性能强但界面迭代速度慢。这套工程的做法是分层解决VB6.0负责窗体、按钮、表格展示、弹窗交互VC6.0负责和设备通信、数据解析、DLL封装。从压缩包的文件名就能看出这层分工。Form1.frm、AddDeviceDlg.frm、ModDeviceDlg.frm、PlanItem.frm、StatusDlg.frm、frmAbout.frm全部是VB窗体的文本文件负责设备管理、计划项配置、状态展示和关于页面。而ComDll.cpp、COMACCESS.CPP、comm.cpp、initsock.h、WS2tcpip.h这些是典型的VC源码处理TCP/IP通信、串口收发和协议解析。其中的netpackage.cap抓包文件进一步说明这套系统在调试阶段是抓过网络包做协议分析的。这种分工的价值在于VB里通过Declare声明就可以直接调用VC编译出来的DLL导出函数。界面层只关心自己拿到的是什么数据不关心数据是怎么从现场设备来的通信层也不关心按钮点了之后界面怎么刷新。我一般会用一句话概括这种结构VB是前台VC DLL是后台Access数据库是档案室。2.2 工程文件里的分层线索从devmon到ComDll压缩包根目录有devmon.dsw、devmon.dsp、devmon.cpp、devmon.h同时又有ComDll.dsw、ComDll.dsp、ComDll.cpp、ComDll.h说明这是两个独立的VC工程被打包在同一个压缩包里。devmon应该是主监控程序的可执行工程ComDll是配套的动态库工程。在VC工程里.dsp是项目文件.dsw是工作区文件.opt和.ncb是辅助缓存Debug目录下放着编译好的中间产物。ComDll.dll和ComDll.lib同时存在说明这个DLL导出的是标准函数接口VB端用Declare加载时配合.lib可以提前确认函数签名。devmon.def和ComDll.def这两个模块定义文件管的是DLL导出函数名老项目通常用这种方式避免C名字改编Name Mangling问题。如果你发现DLL里的函数名带了一堆符号和问号多半是没写.def文件。VB侧的Module1.bas是标准模块存放全局变量和公共函数Project1.vbp是VB工程文件记录窗体列表和引用Project1.vbw是工作区状态文件。DeviceScd.ini和DeviceManager.ini则是设备配置和调度参数的存储文件。整个项目的调用链是VB窗体读INI和Access → 调ComDll.dll接口 → DLL完成TCP/串口通信 → 数据回填到VB界面。2.3 界面层文件与后端功能的对应关系Form1.frm是主监控窗体从文件名上看是系统入口AddDeviceDlg.frm和ModDeviceDlg.frm是设备添加和编辑对话框对应台账管理的增改功能PlanItem.frm处理计划项或轮询计划设置StatusDlg.frm负责设备运行状态展示frmAbout.frm是版本和版权弹窗。jiegouti.TXT应该是结构体定义说明文档这类TXT在国产项目中经常是开发者自己写的接口备忘含金量往往比正式文档高。界面控件的图片资源也很齐全new.gif、email.gif、exit.gif、modify.gif、help.gif、sound.gif、tip.gif这些是工具栏图标control.gif是控制按钮图形ERR.JPG和range.gif应该是错误提示和范围设置界面。光看这些资源就能确认这套监控界面不是摆样子的Demo而是带完整增删改查、报警、帮助、邮件通知的工程化界面。对于想学VB界面布局的人来说这些frm文件可以不装完整环境就打开查看属性设置比如窗体大小、控件坐标、按钮事件绑定逻辑。3. 数据采集链路VC通信DLL怎么接现场设备3.1 网络通信初始化与套接字封装这套系统的通信DLL里initsock.h是做Winsock初始化的WS2tcpip.h是Windows Sockets 2.0的头文件comm.cpp负责串口通信COMACCESS.CPP负责设备访问封装。共同组成了一条完整的采集通道。Winsock初始化的标准写法非常固定第一步是加载WSA库第二步检查版本。老工程师写这个都会先调用WSAStartup然后比对版本号版本协商失败直接WSACleanup退出。WORD wVersionRequested MAKEWORD(2, 2); WSADATA wsaData; int err WSAStartup(wVersionRequested, wsaData); if (err ! 0) { // 加载失败处理非零错误码 return -1; } if (LOBYTE(wsaData.wVersion) ! 2 || HIBYTE(wsaData.wVersion) ! 2) { // 版本不匹配释放资源并退出 WSACleanup(); return -1; }这段代码有两个参数值得注意。MAKEWORD(2, 2)请求的是Winsock 2.2版本如果目标机器只有Winsock 1.1的wsock32.dllLOADBYTE和HIBYTE的校验就会失败。检查和清理顺序不能倒先判断版本合法性再调WSACleanup否则在未初始化状态下调用清理函数会产生不确定行为。套接字创建层面VC的socket函数默认返回阻塞套接字如果没有设置收发超时一旦远端设备断网程序会卡在recv函数里出不来。老项目常见的做法是在创建套接字后立即设置SO_RCVTIMEO和SO_SNDTIMEO选项。这么做的目的是避免单台设备故障拖垮整个采集线程。SOCKET s socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); int nRecvTimeout 3000; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)nRecvTimeout, sizeof(nRecvTimeout)); int nSendTimeout 3000; setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)nSendTimeout, sizeof(nSendTimeout));超时值我一般会设置3000毫秒理由是这个时长足够一个正常响应返回又不至于让操作员等待太久。Modbus RTU这种老协议从设备响应到主站判定超时的窗口通常是500到1000毫秒但TCP链路加上路由跳数3秒是一个保守且稳妥的起点。实际使用中如果网络环境较差可以改成5000再往上就不建议了超过5秒的采集等待会让操作员觉得系统已经死掉。3.2 串口通信参数与翻车教训comm.cpp文件对应串口采集模块。经典串口通信开发一般用CreateFile打开COM口再配合DCB结构体配置波特率、数据位、停止位、校验位。下面是典型配置代码。HANDLE hCom CreateFile(COM3, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hCom INVALID_HANDLE_VALUE) { // 串口被占用或不存在 return -1; } DCB dcb; GetCommState(hCom, dcb); dcb.BaudRate 9600; // 波特率 dcb.ByteSize 8; // 数据位 dcb.Parity NOPARITY; // 无校验 dcb.StopBits ONESTOPBIT; // 1位停止位 SetCommState(hCom, dcb);这里最容易被忽略的是SetCommState调用前必须先GetCommState。我见过有人直接定义DCB然后清零赋值结果串口打得开但收发数据全是乱码原因是DCB结构体里有些保留字段需要保留系统默认值。Unreferenced参数像fOutxCtsFlow和fRtsControl会影响流控COM3如果被别的进程占用CreateFile会返回INVALID_HANDLE_VALUE而不是报错退出后续所有操作都会失效。串口接收还有个常见坑ReadFile在没有数据到达时行为取决于超时设置。如果不调用SetCommTimeouts默认值可能让ReadFile立即返回也可能永久阻塞。正确做法是设置一个合理的总超时时间。COMMTIMEOUTS timeouts {0}; timeouts.ReadIntervalTimeout 50; timeouts.ReadTotalTimeoutMultiplier 10; timeouts.ReadTotalTimeoutConstant 100; SetCommTimeouts(hCom, timeouts);三个参数的含义是间隔超时50毫秒单字节读取系数10固定超时100毫秒。换算下来读取一个8字节完整报文大约需要100到200毫秒这个时间对大多数PLC响应来说足够也不会导致UI假死。3.3 通信线程与UI刷新之间的经典冲突VC通信层属于后台线程VB界面属于前台线程两者交互时必须考虑跨线程数据传递。老项目常用方式有两种一种是VC DLL回调函数通知VB另一种是VB用Timer控件轮询DLL里的共享缓冲区。从文件结构看这套工程更接近第二种因为Form1.frm里大量使用MSWINSCK.OCX控件同时有专门的定时器逻辑配合。MSWINSCK.OCX是Winsock控件的ActiveX版本可以在VB窗体里直接拖拽使用通过DataArrival事件接收数据。但用控件接收数据有个性能上限频率超过每秒50次的数据更新会刷爆事件队列导致界面卡死。如果实时性要求更高常见做法是把VC DLL的接收缓冲区做成环形队列VB侧每隔100毫秒通过导出的函数读取最新一批数据。这样界面刷新频率恒定不依赖网络事件触发。队列读写要加临界区或互斥锁否则偶发崩溃很难复现属于典型的随机翻车现场。4. 监控界面的数据绑定INI配置、数据库台账与报警实现4.1 DeviceScd.ini和DeviceManager.ini到底管什么压缩包里的DeviceScd.ini和DeviceManager.ini是设备调度的核心配置入口。DeviceScd是Device Schedule的缩写管理设备轮询计划包括采集周期、启停时间、联动策略。DeviceManager.ini管理设备基础信息包括设备编号、名称、IP地址、端口号、协议类型、超时时间。老项目的配置文件通常是节段式组织格式如下。[Device_01] DeviceID1 DeviceName一号线PLC DeviceIP192.168.1.20 DevicePort502 ProtocolModbusTCP PollInterval1000 Enable1这种格式的好处是可以在VB里用GetPrivateProfileString和WritePrivateProfileString函数直接读写不依赖数据库就能完成配置修改。设备拔插网络后修改IP地址只需重写INI对应项不用改代码改完重启采集线程即可生效。但INI配置也有边界。它不适合保存高频变化的运行数据因为每次写操作都要落盘频繁更新会拖垮性能。正确分工是静态参数放INI动态运行数据放内存数据库历史数据放Access。4.2 Access数据库在SCADA里的实际分工压缩包里的hui.mdb是Access格式数据库文件不大适合存放设备台账、操作记录、报警历史这类低频写入数据。VB6.0操作Access有两种常用方式DAO数据访问对象和ADOActiveX数据对象。从文件时间看这套工程用的是较早的DAO方式因为引用的控件是MSWINSCK和MSCAL没有看到ADODB相关的引用。ADO的连接字符串写法如下。Dim conn As ADODB.Connection Set conn New ADODB.Connection conn.ConnectionString ProviderMicrosoft.Jet.OLEDB.4.0;Data Source App.Path \hui.mdb conn.Open注意App.Path取的是程序运行目录如果目录路径包含中文或空格老式Jet引擎处理会有问题。解决办法是把数据库放在固定不含中文的路径下或者对路径做一次ShortName转换。数据读取到VB界面的标准方式是通过Recordset绑定DataGrid控件。Dim rs As ADODB.Recordset Set rs New ADODB.Recordset rs.Open SELECT * FROM DeviceTable, conn, adOpenStatic, adLockReadOnly Set DataGrid1.DataSource rsadOpenStatic和adLockReadOnly配合使用是只读快照模式适合设备列表展示不会因为游标锁定导致写入阻塞。如果需要一个可编辑的数据表格应该用adOpenKeyset加adLockOptimistic。这种模式边界必须清楚否则多用户并发编辑会造成记录锁定冲突。4.3 报警逻辑数据到了界面之后怎么处理数据采集上来必须做报警判断Form1.frm和StatusDlg.frm里应该包含报警阈值判断逻辑。老式的报警判断一般不用定时器轮询因为Timer最小精度是15毫秒左右而监控点可能上千个。更常见的是在数据变化时触发判断用VC通信层在解析完报文后直接做阈值比较只有超限值才传给VB界面。报警防抖处理是容易踩坑的点。现场设备的瞬时抖动比如电机启动瞬间的电压波动会让报警频繁触发。经验性做法是连续三次超过阈值才确认报警成立或者设置一个时长为500毫秒的确认窗口。如果阈值设得太敏感操作员会被报警淹死最后把报警音关掉这样真正的异常反而没人注意。报警存储格式推荐使用独立数据表字段包括报警时间、设备编号、报警类型、报警值、阈值、处理状态。这个表要定期归档否则天长日久Access文件膨胀到几百MB查询速度会急剧下降。5. SCADA工程复现避坑清单OCX、DLL调用与老环境兼容5.1 MSWINSCK.OCX未注册导致工程直接打不开现象双击Project1.vbp提示“部件”找不到MSWINSCK.OCX的引用VB6.0在加载窗体阶段直接报错工程根本无法进入设计模式。原因MSWINSCK.OCX和MSCAL.OCX是ActiveX控件用前需要注册到系统注册表。新装系统的机器上默认没有这两个控件尤其是64位系统依赖32位控件时经常出现注册表路径不一致的问题。解决先确认文件在压缩包内手工释放到System32或SysWOW64目录然后执行 regsvr32 命令。64位系统上需要区分32位和64位注册路径。regsvr32 C:\Windows\SysWOW64\MSWINSCK.OCX regsvr32 C:\Windows\SysWOW64\MSCAL.OCX提示VB6.0是32位IDE即使在64位系统上运行也在32位兼容模式下控件必须注册到SysWOW64目录。如果regsvr32注册成功但VB仍报错关闭IDE重新打开一次有些引用列表的缓存要到下次启动才刷新。5.2 DLL返回0不是通信失败而是调用约定不匹配现象VB调用ComDll.dll的某个导出函数程序不崩溃但返回值永远是0或者一个无意义的负数怎么查逻辑都查不出原因。原因VC默认的C调用约定是cdeclVB的Declare默认是StdCall。如果VB声明时没加别名指定调用约定参数入栈顺序一致但栈清理方式不同函数读到的参数可能是错的返回值自然不对。解决VB声明要写成ByVal实参并确认DLL侧导出函数用了标准调用约定。Private Declare Function DevOpen Lib ComDll.dll Alias DevOpen (ByVal nPort As Long) As LongDLL侧.def文件里对应写成DevOpen 1确保导出名是纯函数名。如果函数参数是指针类型VB对应要声明ByRef。数据类型不匹配时VB不会像C那样编译报错而是运行时静默出错这类问题排查难度最大。5.3 Access数据库被锁定导致写入失败现象程序运行一段时间后设备台账保存报错提示“文件正被另一进程使用”但明明只有一个客户端在运行。原因VB通过DAO或ADO连接Access后连接对象未显式关闭记录集仍然挂起数据库文件被Jet引擎锁定。长时间运行的系统里连接泄漏积累到一定数量就会触达锁定上限。解决每次操作完数据库都要养成显式关闭Recordset再关闭Connection的习惯不能用析构函数赌清理时机。代码层面要覆盖异常分支防止抛出错误时连接没有释放。On Error GoTo ErrHandler rs.Close conn.Close Set rs Nothing Set conn Nothing Exit Sub ErrHandler: rs.Close conn.Close Set rs Nothing Set conn Nothing这里On Error的跳转是必须的。老工程里最常见的隐患就是正常路径关闭了连接但某个字段类型异常触发错误后直接跳出了函数连接没有进入清理分支。5.4 Win10与Win11上老控件显示异常现象把工程搬到Win10或Win11机器上运行窗体画面全是错位按钮挤在一起图片显示不全字体比例不对。原因VB6.0窗体是固定像素设计没有做DPI自适应。Win10默认缩放比例是125%或150%窗体被系统按比例拉伸后控件坐标全乱图片资源没有做高清适配。解决最简单粗暴的做法是给exe设置兼容性右键exe属性→兼容性→更改高DPI设置→勾选“替代高DPI缩放行为”缩放执行设为“系统”。代码层面更彻底的方案是进程级别调用SetProcessDPIAware函数但VB6.0要在模块里手动声明API工程侵入性较大建议先试兼容性设置优先级最高的是先确认运行环境是否值得升级。5.5 VC6.0编译后的DLL在新系统上的TCP栈差异现象ComDll.dll编译好之后在旧机器上通信正常换到新机器上TCP连接时不时失败或者收包永远不完整。原因Windows Vista之后TCP栈行为有调整尤其对非阻塞连接和超时处理不同。老代码用BSD socket风格写的连接和关闭逻辑存在时序假设新系统上彻底失效。解决连接前设置非阻塞模式用select做带超时的连接检测而不是直接阻塞等多长时间。这块改动虽然不大但能覆盖大部分新老系统差异场景。u_long nMode 1; ioctlsocket(s, FIONBIO, nMode); fd_set fds; FD_ZERO(fds); FD_SET(s, fds); timeval tv { 3, 0 }; int nRet select(0, NULL, fds, NULL, tv); if (nRet 0) { closesocket(s); return -1; }6. 在现有机器上快速验证这套工程抓包与配置检查两块试金石拿到源码第一步不是改界面而是先让它跑起来采集到真实数据。我用这套工程做验证时习惯分两步走第一步看网络层通不通第二步看设备台账配置对不对。网络层验证用抓包最直接。压缩包里的netpackage.cap就是某个同学或前工程师在调试阶段用抓包工具保存的报文记录你可以用Wireshark打开它看看当时的报文中协议端口、报文长度、功能码分布是什么结构。确认现场协议后把自己机器的通信参数靠拢到抓包文件体现的报文格式上再对照DeviceScd.ini里的IP、端口、协议类型逐项检查。我一般会把设备的IP地址改成抓包文件里的地址先把链路测试通再改成现场实际地址避免配置地址错误和协议错误互相掩盖。设备台账检查的要点是DeviceManager.ini里的Enable标志位。这个标志位是逻辑开关不是物理状态置0的设备不会被采集线程轮询。很多人改了设备IP和端口但忘了Enable还是0现象就是设备明明在线、通信DLL工作正常数据库里就是没有新数据。从那以后我每次排查采集离线问题都会强制走一遍“先看Enable1、再看INI路径下有没有中文、最后抓包确认报文”的套路这三个点查完八成以上的通信问题都能定位到根因。这套工程能给你的不只是VB和VC混编的参考更重要的是老一代监控系统的设计思路界面、通信、存储分层边界清晰设备配置外置报警逻辑独立一切为可维护性服务。希望帮到你。本文还有配套的精品资源点击获取