
简介一份完全开源的 OPC Server/Client 源码工程面向工业自动化领域的 C/C 开发者适合希望深入理解 OPC 通信协议、阅读源码学习实现细节或进行二次定制的学习者相比 lightOPC该项目源码结构清晰更易上手且包含服务端与客户端两侧完整代码。资源包共 114 个文件压缩包仅 205KB以 65 个头文件、26 个 C 源文件和 5 个 C 文件为主体另有 Visual Studio 解决方案与工程文件、OPC 接口实现代码及 Client.test/Server.test 测试工程可完整编译构建并直接在本地环境调试运行。目前已有 1262 人学习/下载。通过研究这份源码可以掌握 OPC Server 与 Client 之间的数据交换机制、订阅与读写操作、事件处理和多线程并发控制示例工程可用于验证通信流程与错误处理逻辑对于想要从零开始理解 OPC DA/UA 模型或在自定义系统中集成 OPC 通信的开发者是一份很有价值的参考。 干工控上位机这行的十有八九都动过“自己写一个OPC Server”的念头。我之前被项目逼着找OPC server 源代码做参考找了一圈发现要么收费要么源码老得没法看。后来翻到OPCWorkshop这个完全开源的工程一口气把OPC DA那套COM逻辑读通了。它给我的感觉是比网上流传的lightOPC更容易看代码结构清楚能直接编译、注册、连接特别适合想理解OPC Server内部机制的人。这篇文章就把我读这套源码、编译跑通、二次开发踩坑的整个过程整理出来给准备入手OPC Server开发的朋友一个参考。1. 项目初印象OPCWorkshop到底是个什么东西1.1 工控人为什么总想找一份OPC Server源码OPC在工业现场太常见了但大部分工程师只是OPC客户端的使用者服务器端通常是购买商业组件或者用开发包封装。真到要做定制功能比如把设备私有协议直接映射成OPC变量、做统一数据网关、搞特殊的读写权限控制黑盒组件就会让人很被动。这时候最需要的是一份能看懂的OPC Server源码可以在里面改数据源逻辑、调整接口实现甚至拿它做教学。问题是OPC规范本身非常庞大COM接口、组对象、项管理、异步回调、DCOM配置叠加在一起没有良好的示例根本啃不动。网上流传的lightOPC确实很经典很多老项目都在用但它代码风格偏老变量名简短逻辑挤在一起想在里面加一个自定义设备驱动你得先花很长时间理解它自己的框架。OPCWorkshop不一样它更像一份“带注释的教科书工程”把Server端和Client端都放在一起目录清楚类职责也接近OPC官方文档的划分。我拿到它的第一反应是这代码是真的打算让人看的。1.2 源码目录与项目定位OPCWorkshop是一个比较老的完全开源项目主打轻量级OPC Server实现。常见的版本一般会包含服务端工程、客户端工程和公共头文件有些还包括一个模拟设备的数据源模块。它用的还是OPC DA 2.0那套COM技术栈不是OPC UA。这个定位在今天看有点复古但也正是这个原因它的代码量不会大到失控非常适合用来理解OPC DA服务器端的完整生命周期。从目录结构上看服务端的核心类一般可以分成几个职责块一是COM工厂和组件注册相关负责让Windows系统认识这个COM对象二是OPC Server主对象实现IOPCServer、IOPCBrowseServerAddressSpace这些公开接口管理所有的Group三是OPC Group对象负责管理Item集合、读写模式和回调四是数据源部分负责把模拟PLC、寄存器表或者真实设备的数据包装成Item值。这种分层方式非常接近官方文档里面的对象模型读代码时能直接对着规范理解不用来回跳。2. 和lightOPC同台比一比可读性差在哪2.1 lightOPC真的不能打吗lightOPC在工业圈里名声很大很多人在找“轻量OPC开发包”时都会看到它。它的优点是可以快速出一个OPC Server文件不多依赖也少集成进自己的上位机项目比较方便。但它最大的问题是代码太紧凑了为了追求轻量函数内部嵌套很深很多关键状态通过全局变量或成员标志位传递类的边界也比较模糊。我初读lightOPC的时候花在“找这个变量在哪被修改”上的时间远远超过理解OPC接口本身的时间。不是说lightOPC不能看而是它的定位是“能用就行”不是“方便学习”。如果已经对OPC DA很熟想提取其中某一小段代码lightOPC可以当工具书但如果你是个新手想从头理解Server、Group、Item是怎么创建的回调是怎么触发的lightOPC的阅读曲线会比较陡。OPCWorkshop虽然在工程结构上也不是现代C范儿但它的源码里有清晰的类划分和相对明确的调用关系我读下来明显感觉顺畅很多。2.2 OPCWorkshop的代码组织好在什么地方OPCWorkshop的代码组织有几个让我印象很深的地方。第一它把COM接口实现和业务逻辑做了最基本的分离。每个OPC对象都用一个类去管理生命周期接口方法里不会塞一堆设备逻辑数据源部分反而独立在另一个模块里。这意味着你想把它改成连接真实PLC只需要替换数据源那一块OPC主流程几乎不用动。第二Server、Group、Item之间的关系在代码里看得很直观。创建Group时Server对象会维护一个Group列表创建Item时Group对象会维护Item列表。这种关系在文档里是层级树但实际代码很容易写成一团OPCWorkshop的做法比较老实数据结构清晰遍历、查找、删除都有迹可循。第三它自带了Client示例。这一点相当关键。你看服务端代码时会疑惑“这个接口是干嘛的”直接跑到Client工程里看它是怎么调用的马上就明白了。双向印证比单看服务端代码效率高很多。lightOPC的资料里虽然也有示例但很多版本更倾向于演示怎么调用组件而不是把两边的实现对照起来看。对比项OPCWorkshoplightOPC源码完整度Server和Client示例齐全以Server组件为主示例相对精简代码结构类职责清晰分层明显轻量优先逻辑相对紧凑OPC接口覆盖常见的DA 2.0接口都有以核心读写接口为主上手难度对新手友好适合学习适合有一定基础的人做提取二次开发方便替换数据源模块改造需要先理解内部框架维护活跃度偏老更新少同样偏老经典但更新少3. 读源码之前先把OPC Server的几个关键概念弄明白3.1 Server、Group、Item三层模型读OPCWorkshop之前如果没搞明白OPC DA三层对象模型很容易被代码绕进去。简单来说OPC DA的服务器端对外暴露的是Server对象一个Server可以包含多个Group一个Group可以包含多个Item。用数据库做类比Server就是数据库服务Group是建立的连接会话Item是表里的字段。客户端要先连接Server再创建Group然后在Group里添加自己关心的Item后续读写都是围绕Group进行的。这里的Item并不是一个独立COM对象它更像Group内部维护的一个条目。OPCWorkshop里对Item的处理方式也符合这个特征Item数据保存在Group的成员容器里每个Item存了变量名、数据类型、当前值、读写权限、时间戳和质量戳。理解这一点就能明白为什么读取一个Item值时要先从Group里定位Item再通过同步或异步接口取数。3.2 COM/DCOM和OPC的关系OPC DA 2.0基于COM所以OPCWorkshop源码里到处是GUID、IUnknown、IClassFactory、IDL接口定义。很多初学者在这里被卡住觉得OPC怎么这么复杂。其实可以把COM理解成Windows下的一种组件通信协议服务端把功能开放的接口定义清楚注册到系统里客户端通过CLSID找到这个组件调用接口方法。OPC只是在COM上面约定了业务接口比如IOPCServer用来管理Group、IOPCItemMgt用来管理Item、IOPCSyncIO用来同步读写。DCOM就是跨机器版COM它让OPC客户端可以访问远程电脑上的OPC Server。OPCWorkshop本身不一定把DCOM配置细节写得很细但代码里的注册、启动方式会直接影响你在本机调试是否顺利。我的建议是初学者先在本机跑通不要一上来就搞DCOM跨机访问。本机跑通之后再看DCOM的配置网络权限、身份验证、防火墙这些坑会少踩很多。3.3 一条主线读源码的路线我读OPCWorkshop源码时的主线是先找DLL入口或EXE入口看组件是怎么注册的然后看类工厂搞清楚客户端new一个Server对象时代码到底创建了什么接着看Server对象里实现了哪些接口其中IOPCServer和IOPCBrowseServerAddressSpace最重要再往下看Group对象是怎么被创建出来的Group里怎么管理Item最后看同步读写和异步回调的链路。按照这个顺序一整条数据通路就能串起来。具体到代码位置可以先搜索接口名比如IOPCServer然后顺着接口实现类往下走。OPCWorkshop里一般会有类似CServer、CGroup、CItem这样的类命名虽然不同版本略有差异但基本逃不出这个规律。遇到一个接口方法时不要急着看全部实现先确认它是由谁调用、在什么时机调用再进入方法体。这样读代码比从头到尾顺序扫一遍高效得多。4. 把OPCWorkshop编译跑通的全过程4.1 编译前的环境准备OPCWorkshop是老工程直接拿到新版Visual Studio里编译大概率会报错这不是源码问题是老工程对新编译器的兼容问题。我用的环境是Windows 10加Visual Studio 2015打开工程文件后会被提示做一次工程升级选默认转换即可。如果编译过程中遇到stdafx.h、atlbase.h这类头文件找不到的问题一般就是SDK版本或者ATL组件没装全。解决办法是检查一下“C桌面开发”工作负载里的Windows SDK版本选一个和工程匹配的SDK比如8.1或10.0。还有一个容易被忽略的点是字符集。老工程的默认字符集可能是多字节字符集新版工程默认是Unicode。如果编译时一堆字符相关报错可以直接在工程属性里把字符集改成“使用多字节字符集”或者反过来视原始代码而定。总之编译老工程的通用套路是先把平台工具集调低再把运行库调成多线程调试版最后处理编译报错。不要指望一步到位耐心点看编译器提示。4.2 编译和注册的实操步骤具体操作时我会先编译整个解决方案看Server和Client是不是都能过编译。如果有工程缺失或路径不对会收到类似“无法打开源文件XXX.h”的错误这时要检查所有引用工程的依赖关系确保Server工程先编译。如果只是某一个源文件报语法错误大多是不兼容C标准或者头文件版本引入的删掉过时的强制类型转换提示用新版语法替换即可。编译成功后最关键的一步是把生成的Server DLL或EXE注册到系统。OPC DA 2.0组件的注册方式有两种如果是DLL形式在管理员命令行里执行regsvr32 路径加DLL名称如果是EXE形式运行一次EXE并带/regserver参数或者直接双击后让程序自注册。注册完成后可以在注册表里搜索对应的CLSID确认类型库和AppID是否生成。如果这一步没做客户端会直接提示“服务器未注册”或“未找到指定的组件”。4.3 最小客户端验证流程服务端注册完成后我习惯用自带Client示例先做一次最小验证这样能把变量范围缩小。启动Client后通常第一步是连接本机Server输入ProgID或从列表中选择。连接成功后创建一个Group然后在Group里浏览Server的地址空间。OPCWorkshop一般带了模拟数据源里面会有一些类似Device1.Tag1的测试节点把它们添加到Item列表里然后执行一次同步读如果客户端能看到实时数值并且变化说明整条链路已经通了。如果自带Client不方便也可以用通用OPC客户端工具连。我用过UAExpert的OPC DA模式也用过Matlab的OPC工具箱。需要注意客户端位数要和Server一致32位Server尽量用32位客户端测试64位环境也优先保证位数匹配。跨位数访问不是不行但容易增加DCOM配置的复杂度初学时没必要给自己找这个麻烦。5. 踩坑记录常见问题与排查技巧实录5.1 注册不了、类型库加载失败编译注册阶段最容易遇到的问题是regsvr32弹出“模块加载失败”或“DllRegisterServer入口点未找到”。模块加载失败大概率是因为编译产物缺少运行库依赖或者当前进程位数不匹配。可以先下载Dependency Walker这类工具看DLL依赖也可以直接在“事件查看器”里看加载失败的模块名。如果是入口点未找到多半是工程没有正确导出COM注册函数需要检查工程的模块定义文件或者链接设置。注册时还有一个隐蔽问题Visual Studio链接的C运行时是动态库目标机器上如果没有安装对应的VC Redistributable注册也会失败。开发机上一般都有运行库但测试机上不一定。把编译模式改成静态链接运行库或者给测试机装一次运行库都能解决。我的习惯是本地调试用动态库做安装包时再单独带一份运行库避免调试阶段多一堆变量。5.2 客户端找不到Server客户端连接时如果看不到OPCWorkshop的Server条目最常见的三个原因一是服务端没有成功注册ProgID没有写进注册表二是OpcEnum服务没有启动或没有注册OPC客户端需要枚举服务端的OPC Server列表三是DCOM和防火墙把跨进程调用拦住了本机测试时也偶尔会遇到权限隔离问题。排查顺序建议是先确认注册表搜索项目里的ProgID看看有没有对应的CLSID再检查系统中的OpcEnum服务这个COM组件在Windows中默认可能存在但如果系统精简过很可能缺失最后再看DCOM配置调整交互用户和启动权限把“身份验证级别”设为默认或无。记住一点本机测试时尽量用管理员身份运行客户端能省掉很多权限坑。5.3 Item列表正常但读取不到值如果客户端能浏览到Item但同步读返回失败或数值一直为0问题一般出在数据源部分。OPCWorkshop自带的模拟数据源通常有一个独立的仿真线程如果这个线程没有启动或者它维护的寄存器表没有被初始化Item值就会一直是空的。检查时先看服务端有没有启动模拟线程的开关再看寄存器表有没有默认值。另一个常见问题是数据类型不匹配。OPC Item在创建时会指定Variant类型比如VT_I2、VT_R4、VT_BSTR。如果模拟数据源里存的是浮点数但Item创建时标成了整数读取时类型转换就会出错或截断。遇到这种情况可以看客户端的Item属性设置把请求的数据类型换成和模拟数据源匹配的类型再读一次。我在调试中常用的方法是给模拟数据源加一个固定递增计数器如果客户端读到的值按照预期变化说明数据通路完全正常问题只在数据源初始化。现象可能原因快速检查处理方法regsvr32加载失败缺少运行库、位数不匹配看加载失败模块装运行库或改链接方式客户端枚举不到Server未注册、OpcEnum缺失查注册表ProgID重新注册、恢复OpcEnum浏览不到Item地址空间未初始化查看服务端日志启动模拟数据源同步读返回失败数据类型不匹配检查Item类型修改类型或转换逻辑数值固定不变化仿真线程没跑打断点看线程状态启动或重置线程6. 复盘和一些可以继续做的方向6.1 这套源码放在今天的价值很多人一看到OPC DA就觉得过时了加上现在OPC UA是主流就觉得没必要再碰老代码。我的看法不太一样。OPCWorkshop的价值不在于提供一个现代化生产级Server而在于它是理解整个OPC服务器端机制的最小可运行样本。OPC DA的很多概念比如对象模型、浏览地址空间、分组管理、订阅回调在OPC UA里依然能找到对应关系。把OPCWorkshop读熟之后再看OPC UA服务端你会觉得“底子上是通的”剩下的只是传输协议和数据模型的变化。而且在实际项目中老设备、老上位机仍然大量跑着OPC DA。遇到现场问题手上有一份能看懂的源码比对着商业组件黑盒猜半天要靠谱得多。我就是靠这套源码定位过一次“服务端自动消失”的问题最后发现是COM引用计数导致进程退出卡了两天的问题半天就解决了。6.2 如果我来做二次开发会怎么改如果真要基于OPCWorkshop做点实事我第一件事是把数据源模块换成自己设备的协议驱动比如Modbus RTU或者自定义TCP协议。这个改造思路很清晰OPCWorkshop的OPC逻辑不用大动只需要把模拟寄存器表的读写函数替换成协议收发函数在后台开一个采集线程更新寄存器值然后让Item读取时返回最新值即可。这是这套源码最值得利用的地方。第二个方向是给Server加一个诊断界面或者日志模块。OPCWorkshop原型里对运行状态的输出比较弱二次开发时可以在关键入口点加日志比如Group创建、Item添加、同步读、异步回调。有了日志后面做DCOM部署或者性能调优会轻松很多。当然如果是新项目我更推荐在此基础上移植到OPC UA开源界有open62541这种成熟库把OPCWorkshop的建模思路拿过去开发效率会比从零看UA规范高不少。如果让我给后来的朋友一个建议别把OPCWorkshop当成可以直接上生产的成品把它当一本带注释的教科书。读懂了再动手真的比对着lightOPC猜半天要省时间。这是我花了一周把数据链路跑通之后最直接的体会。本文还有配套的精品资源点击获取