简介:opcworkshop是一套完全开源的OPC服务器与客户端实现源码,面向工控上位机开发者及对OPC协议感兴趣的C/C++程序员,可用来理解OPC DA/AE数据访问机制、事件订阅与通报流程,也可作为二次开发基座。压缩包共114个文件,约205KB,以h头文件和cpp源文件为主,辅以c文件、vcproj工程文件及sln解决方案;头文件划定了OPC接口、数据结构和GUID定义,源码则覆盖服务端启动、客户端调用、测试工程等关键环节。浏览学习人数已超1200人,整体结构紧凑、依赖较少,适合从源码层面快速上手。通过研读这份代码,可掌握OPC服务器如何暴露数据、客户端如何读写订阅,以及COM/DCOM环境下接口调用与事件处理的实现细节,对排查工业通信问题或自研轻量OPC模块都很有帮助。 工业上位机开发这几年,绕不开的一个东西就是OPC。很多项目里PLC数据要往MES、SCADA里送,甲方动不动就甩一句"你们提供OPC Server就行",可真到自己从头开始写一个OPC Server时,大部分人都懵了:COM那套接口看着像天书,文档全是英文,样例代码又老又绕。我前前后后啃过好几份开源的OPC Server实现,包括网上经常被提到的lightOPC和另一个更完整的项目OPC Workshop。如果让我给想入门OPC Server源码的人一个建议,我的答案很直接:直接看OPC Workshop,它比lightOPC更适合用来学习,代码结构清楚,工程组织也接近真实产品。
这篇文章我不打算讲"OPC是什么"这种基础概念,而是直接站在"我要把一个开源的OPC Server源码读明白、跑起来、改着用"的角度,把OPC Workshop这个项目的学习方法、源码组织逻辑、编译踩坑和扩展方向一次说清楚。
1. 入手OPC Server源码之前的三个认知准备
1.1 OPC DA不是"协议",而是一组COM接口约定
很多刚接触OPC的人会有个错觉,以为OPC和Modbus一样,是一种基于报文的通信协议,比如通过TCP发一帧数据过去,设备返回一帧。其实OPC DA(Data Access,数据访问)本质上不是协议,而是微软COM/DCOM技术之上的一组接口规范。OPC Server是一个COM组件,OPC Client通过COM接口调用Server的方法,比如"给我创建一个Group""往Group里添加一个Item""把这个Item的值读出来"。
为什么要先搞懂这一点?因为你看源码时,所有代码都会围绕COM展开:类工厂、接口实现、IUnknown、IDispatch、注册表、GUID、类型库。如果你不理解COM的基本套路,打开OPC Workshop源码第一眼就会被ATL的宏吓退。反过来,只要你知道"COM接口就是一张函数表,Client拿到的其实是一个接口指针,调用接口方法最终会执行到Server里你实现的那个函数",那整个代码就能拆开看。
我在实际带人看代码时,会先让对方在项目里搜STDMETHODIMP这个宏,把所有带这个宏的函数都列出来,这就是一个OPC Server对外提供的所有能力的入口。比对着规范文档一页一页看高效得多。
1.2 三层对象模型:Server、Group、Item
OPC DA的地址空间是一个标准的三层模型:OPCServer、OPCGroup、OPCItem。
- OPC Server是顶层对象,负责管理Group的生命周期,也是Client连接时最先拿到的对象。一个Server可以包含多个Group。
- OPC Group是Item的容器,它的一个重要职责是设定采集/回调周期。同一个Group里的Item共享一个更新频率,这在做性能优化时非常关键。
- OPC Item对应具体的一个数据点,比如"1号锅炉的温度""3号电机的转速"。它本身不存数据,它只是"指向"Server内部数据源的一个句柄。
这个模型是整个源码的骨架。OPC Workshop的类设计也基本沿用了这个结构,你会在代码里看到类似COpcServer、COpcGroup、COpcItem这样的类名。理解这个三层关系之后,再去看接口实现就不会迷路。
1.3 数据流:客户端轮询 vs 订阅回调
OPC DA的数据读取有两种模式,源码里对应着两套不同的接口实现。
一是同步读写(IOPCSyncIO),Client发起Read操作,Server直接返回当前值。这种模式简单,但Client要不停地主动去问,网络开销大,适合点位少的场景。
二是异步订阅(IOPCAsyncIO2、IOPCGroupStateMgt配合连接点),Client告诉Server"你每隔XX毫秒把数据推给我,值变了就通知我"。这才是OPC真正好用、高效的地方,也是现场SCADA系统最常用的方式。
读OPC Workshop源码时,我建议先看同步读写那条链路,因为代码路径短、没有回调逻辑,容易建立信心。等同步跑通了,再去看异步的实现,此时你会接触到IConnectionPointContainer、IConnectionPoint、客户端接收回调的IOPCDataCallback接口,理解难度会陡增,但一旦啃下来,对COM事件模型的理解也会上一个台阶。
2. OPC Workshop源码结构导览:先看哪个项目,再看哪个文件
2.1 项目整体划分
OPC Workshop虽然是开源项目,但它的工程组织比一般Demo要认真很多,这也是我说它比lightOPC容易看的核心原因之一。整个解决方案里通常分成这样几类项目:
- 核心Server实现工程:这是最关键的工程,里面实现了OPC DA规范要求的COM接口,编译出来是一个DLL/EXE。
- 模拟设备/数据源工程:提供后台数据,比如用定时器随机生成一批模拟数值,模拟PLC的寄存器变化。
- 配置/注册工具:用于在Windows上注册COM组件、生成类型库,有的版本还带一个简单的配置界面。
- 测试客户端工程:一个自己写的OPC Client,用来连接本机Server做验证。
拿到源码第一步,不要急着打开某个.cpp文件通读,先在解决方案资源管理器里把所有工程过一遍,搞清楚哪个是Server、哪个是Client、哪个是工具。我见过不少人一上来就在一个上千行的文件里死磕,磕完发现那只是个工具类,这种挫败感非常打击学习热情。
2.2 从注册表到内存里的Tag表
一个OPC Server启动后,Client要找到它,靠的是Windows注册表和COM的类工厂机制。OPC Workshop作为一个标准COM组件,它的Server端有一个关键函数:DllRegisterServer。这个函数做的最重要一件事就是把这个组件的CLSID、ProgID写入注册表。
看代码时你可以这么定位:先找到CComModule或_AtlModule相关的注册逻辑,再看类工厂和UpdateRegistry宏。这些代码会告诉你这个OPC Server的ProgID叫什么、CLSID是多少。以后你在自己的客户端程序里new Guid(...)或指定ProgID去连接时,底层就是去注册表里找这个CLSID。
Server内部还有一个非常重要的数据结构,我把它叫做"Tag表"。OPC Workshop内部会维护一张表,记录当前有多少个Item、每个Item叫什么名字、当前值是多少、质量戳(Quality)是什么、时间戳是什么。你无论从哪个接口进来,最终都是在这张表上做增删改查。找到这张表的定义,基本就找到了Server的核心数据模型。
2.3 关键接口的实现逻辑
我把OPC Server源码里值得重点跟读的接口按建议顺序列一张表,每个接口对应Client端的一个核心能力:
| 接口/函数 | 作用 | 学习建议 |
|---|---|---|
IOPCServer::AddGroup | 创建Group,是整个会话的起点 | 先跟读,理解Group是如何持有Item集合的 |
IOPCItemMgt::AddItems | 往Group里添加Item | 重点看它如何处理无效Item名、客户端句柄与服务端句柄的映射 |
IOPCSyncIO::Read/Write | 同步读写Item的值 | 最简单,适合第一个跟读完整调用链 |
IOPCGroupStateMgt | 修改更新周期、激活/停止 | 理解回调的数据源周期控制 |
IConnectionPoint相关 | 异步通知机制 | 最难,放到最后啃 |
有一类坑会反复出现:句柄映射。Client和Server各自维护一套句柄,Client传进来的hClient是它自己用来标识Item的,服务端必须原样保存并在回调时原样返回给Client,否则客户端就没办法知道这条数据属于哪个标签。OPC Workshop源码里对这套映射处理得很规范,值得专门花时间看一遍——很多自己写的OPC Server出诡异问题,都是在这上面栽了跟头。
3. 把OPC Workshop编译跑通,在客户端里完成一次真实读写
3.1 环境准备与编译顺序
OPC Workshop是基于传统Windows COM/ATL技术写的,编译环境选择上没有太多花活,Visual Studio基本都行。需要注意的点是项目里可能用到了旧版字符集,编译前要把项目的字符集改成"使用多字节字符集",否则很多带char*的代码会报错。另外,因为涉及COM和类型库生成,建议关闭预编译头的项目单独检查一下,如果用VS打开后发现大量文件报找不到头文件,先看下是不是Windows SDK版本选得太新。
编译顺序上,我习惯先把核心Server工程单独编译一遍,确认代码本身能编过,再去编测试客户端。如果一起编译报错,你很难判断问题出在代码还是工具链。
3.2 DLL注册与DCOM配置
编译成功后,接下来是注册。以管理员身份打开命令行,执行regsvr32注册生成的DLL。如果看到弹窗提示注册成功,那么可以继续;如果报模块加载失败,多半是缺少运行时依赖,比如没装对应的VC++运行库。
注册完别急着测,强烈建议先检查一下DCOM配置。OPC DA基于DCOM,在Windows 10及以后的系统上,默认DCOM安全策略经常会让Client连不上Server。你需要打开dcomcnfg组件服务,找到这个OPC Server的组件,在属性里设置:
- 身份标识:如果只是本机测试,选"交互式用户"最省事。
- 启动和激活权限:把当前登录用户加进去,给"本地启动""本地激活"权限。
- 访问权限:同样把用户加入,给"允许访问"。
顺带一提,如果后面你打算在另一台机器上跑Client远程连这台Server,那要额外处理防火墙和DCOM端口,这也是OPC DA最烦人的地方之一。OPC Workshop的学习阶段建议先在本机跑通,远程问题等完全理解代码后再去折腾。
3.3 用客户端验证:模拟一次完整会话
编译好测试客户端并连接本机Server后,我们需要完整走一遍OPC DA会话的标准流程,这段流程无论在哪套OPC系统里都通用:
- 创建OPCServer对象实例,调用
Connect。这一步底层就是COM的CoCreateInstance,去注册表找到Server的CLSID并创建对象。 - 调用
AddGroup创建一个Group。此时需要传入更新周期(比如100ms)和客户端组句柄。 - 调用
AddItems往Group里添加若干Item,比如"Random.Int1"和"Random.Real1"这组模拟标签。返回后,每个Item会有一个服务端句柄(hServer)。 - 调用
SyncRead读取这几个Item的值。此时如果能拿到非零的数值、质量戳为192(质量好/正常),说明Server的同步读已经完全打通。 - 再调用
AsyncRead或开启订阅回调,观察Server端打印出的日志,确认数据是按设定周期在推送。
我在现场调试时遇到过很多次"AddItems成功,但Read出来质量戳是0"的情况,这通常不是通信的问题,而是Server内部的Item和真实数据源没绑定上。OPC Workshop因为是模拟数据源,理论上不会出现这种问题,但你将来替换成真实PLC数据时一定会遇到。到时记住一个排查口诀:先看Item名在Tag表里是否存在,再看数据源有没有在刷新,最后回来看质量戳。
4. 为什么说它比lightOPC更容易看
4.1 项目组织方式上的差距
lightOPC在开源社区里名气不小,很多人第一次接触OPC Server源码就是看它。它的优点是代码量少,能把一个OPC Server最精简的骨架在几千行里讲完。但"精简"也是一把双刃剑:你会发现很多接口只是给了一个空实现,业务逻辑和COM基础机制混在一个文件里,看到后面很难分清"这是OPC规范要求的东西"和"这是具体业务逻辑"。
OPC Workshop的思路不一样。它把COM机制、OPC接口实现、数据源模拟、客户端演示拆成了不同的模块,每个模块的职责边界比较清楚。你有任何想研究的问题,都能快速定位到对应的工程里去,而不是在一堆文件里大海捞针。我自己的体会是:读lightOPC像是看一个简笔画,能一眼看到轮廓,但看不到肌肉和骨骼;读OPC Workshop更像看一具教学用的完整骨架模型,结构清清楚楚,每个部件都能拆下来单独研究。
4.2 学习路线的差异:骨架 vs 完整工程
如果你的目标只是"搞清楚COM组件的创建和Release流程",那lightOPC完全够用,甚至更快。但如果目标是"能独立开发一个可维护的OPC Server",或者"在公司项目里基于开源代码二次开发",我强烈建议从OPC Workshop入手。
原因很简单:真实工程项目里,OPC Server不只是"实现接口"那么简单。它要有配置管理、数据源的接入抽象、日志、异常处理、性能优化这一大堆周边。OPC Workshop在这些方面虽然比不上商业产品,但它至少提供了一种"正规军"的工程写法,Copy它的模块切分思路,比从零开始组织代码要靠谱得多。
另外,OPC Workshop带了测试客户端,这一点我很看重。学习通信类代码,有一个能直接上手的调试客户端,比看一百页文档都管用。你可以随时修改Server端的代码,然后跑到客户点里验证结果,这种"改代码-编译-验证"的闭环,是学习速度的保证。
4.3 各自的适用场景
我把这两个项目的定位总结一下,方便你按需选择:
| 维度 | OPC Workshop | lightOPC |
|---|---|---|
| 代码量 | 中等偏大 | 很小 |
| 工程完整度 | 接近小型产品 | 最小可行原型 |
| COM机制讲解 | 有清晰的模块划分 | 混杂在一起 |
| 调试体验 | 自带客户端,方便验证 | 需要自己另写客户端 |
| 适合场景 | 二次开发、系统学习 | 快速理解COM/OPC最小骨架 |
如果你只有一晚上,想看明白OPC到底是怎么回事,看lightOPC;如果你有一个月,想真正把OPC Server吃透并改造成自己的东西,看OPC Workshop,这个投入非常值。
5. 读完之后,往真实项目扩展的三个方向
5.1 把模拟数据源替换成真实设备采集
OPC Workshop自带的数据源是模拟的,读懂了之后,第一件值得做的事就是把它替换成真实数据。比如通过Modbus TCP去采集PLC里的寄存器,再把寄存器值和OPC Item一一对应起来。
这一步做下来,你会真正理解OPC Server在工业系统中的位置:它本质上是个"协议转换+数据网关"。一边对接各种工业协议(Modbus、Profinet、IEC61850),另一边对外提供统一的OPC接口。OPC Workshop的价值在于把"对外提供OPC接口"这半边已经写好了,你只需要把"对接工业协议"那半边填进去。
我个人建议你在改数据源时,把Tag表的读写接口提取出来,定义成一组统一的抽象接口。这样以后换协议、加设备,都只是新增一个数据源驱动的事情,不用动OPC接口那层代码。这个架构上的好处,会随着接入设备变多越来越明显。
5.2 从OPC DA向OPC UA迁移的思路
现在工业现场越来越倾向于用OPC UA,因为UA跨平台、不用DCOM、安全性好。OPC Workshop这一套基于COM的DA技术,将来大概率会被UA取代。但学DA源码对理解UA仍然很有帮助——UA虽然实现机制完全不同,但信息模型中的Server、对象、变量、订阅这些核心概念和DA是承袭演进的。
如果你想从这套源码往UA方向走,我建议不要直接改造它,而是先移植思路,再移植代码。UA有更成熟的第三方开源库,比如open62541和UA-.NETStandard,你可以用OPC Workshop的模块划分方式来重新组织一个UA Server工程。之后想让DA和UA共存,就做一个网关或桥接层,把DA Server的数据转发到UA Server里。我在实际项目中就是先用DA把老设备数据统一收进来,再用UA把数据模型暴露给上层系统,这个过渡方案非常稳。
5.3 工程化改造:日志、安全与性能
OPC Workshop作为教学项目,日志和安全这两块相对基础。如果你要在生产环境二次开发,建议优先补这三点:
- 日志:每一条Client连接、Group创建、Item读写、回调失败都要留痕迹。COM接口常见死锁和句柄泄漏很难查,很多时候只能靠日志反推时序。
- 安全:OPC DA的DCOM安全配置很繁琐,生产环境至少要做到按用户授权,不同用户只能访问指定Group。这个逻辑可以在
AddGroup之前的身份验证环节插入。 - 性能:如果点位多(上千个Item),要关注数据快照的锁粒度。OPC Workshop这种教学代码往往全局一把锁,点位多了以后性能会崩。改造方向是分组加锁,按Tag表分区管理,减少锁竞争。
我见过不少二次开发项目死在性能和日志上,反而是功能接口很快就调通了。所以我的建议是:读懂OPC Workshop只算第一步,把它的工程化短板补上,才算真正具备上线交付的能力。
最后再分享一个我用了很久的学习方法:拿到一份开源代码,第一遍不要追求读懂每一行,而是跟着一条用户请求的路径把关键代码串起来。对OPC Workshop来说,就是"Connect -> AddGroup -> AddItems -> Read -> 下线"这条路走一遍,给它起个名字叫"主链路"。主链路通了,你再往两边扩,去看异常分支、看异步回调、看配置加载。这个思路对看任何通信框架都适用,希望你也能在用这套方法啃源码时少走弯路。
本文还有配套的精品资源,点击获取