☰
C#上位机OPC通信实战:OPCAutomation.dll连接PLC与数据采集
2026/10/7 6:21:14 网站建设 项目流程

简介:一份面向C#开发人员的OPC通信实操资源,基于OPCAutomation.dll封装工业自动化通信逻辑,覆盖S7200、S7300、S7400系列PLC的数据读写场景,既适合新手快速上手OPC客户端开发,也适合有一定经验的工程师参考工程组织方式。资源共32个文件,压缩包约171KB,包含9个cs源码文件、可直接运行的exe程序、OPCAutomation.dll及Interop互操作文件,还有sln/suo等Visual Studio工程配置,以及resx/resources资源、pdb调试符号、tlog构建日志等,完整还原了一个可编译调试的C#项目结构。目前已有871人学习浏览。通过这份压缩包,用户可以获得完整的OPC客户端示例工程,了解如何引用OPCAutomation.dll、初始化OPC服务器连接、读写PLC数据并处理交互结果;工程内附带的源程序和配置文件便于直接修改复用,可快速迁移到自己的工控项目中。

1. C#操作OPC与OPCAutomation.dll:为什么是这套组合,以及它解决什么问题

做上位机的人迟早会面对这样一幕:客户车间里摆着西门子PLC、几台数控机床、一堆带传感器的检测设备,每台设备都有自己的通信协议,上位机要取的运行状态数据却分散在十几个协议里。OPC DA就是这一层做统一采集的标准协议,而OPCAutomation.dll是C#里最常见的OPC客户端接口。它能让你用几十行代码连到OPC服务器、批量读标签、往设备写值,不用去啃每个产线的私有驱动。这条路线特别适合还在用Winform做上位机、设备端又没法统一升级的情况。我下面会把环境准备、读写实现、工程源码怎么组织,以及实战里踩过的坑一次讲完。

2. OPCAutomation.dll到底是个什么:COM包装、环境准备与最小连接代码

2.1 先分清OPC服务器、OPC客户端和OPCAutomation.dll

OPC是一套工业通信标准。说“连接OPC”的时候,实际上要分清楚三个角色:提供数据的OPC服务器、发请求的OPC客户端,以及客户端和服务端之间走的接口。常见的OPC服务器有Kepware KEPServerEX、西门子SIMATIC NET、Matrikon OPC Simulation,它们负责和PLC、数控机床、传感器驱动通信,把底层乱七八糟的协议翻译成统一的OPC数据项。你的C#上位机是客户端,它只需要知道标签名就能读到值,不用管设备是Modbus还是S7协议。

OPCAutomation.dll是OPC基金会发布的自动化接口包装,准确叫法是OPC Automation 2.0接口,它把COM世界里那套繁琐的接口调用包装成OPCServer、OPCGroup、OPCItem这样的对象。这也是它在C#里容易上手的原因:你new一个OPCServer,调Connect,加Group加Item,直接拿Value。对做c#上位机的人来说,这套东西学习成本比直接用OPC DA的C++接口低得多,也比上手OPC UA简单。要注意的是,OPCAutomation.dll本身不是OPC服务器,不提供数据,它只是一个连接通道。很多人第一次用的时候,以为装了这个DLL就能读PLC,结果发现还要先有一个OPC服务器在运行。这个顺序搞反了,后面全是坑。

选型角度说,如果车间里都是老式PLC和数控机床,厂家现在还在给OPC DA服务器做维护,用OPCAutomation.dll是最省事的路线;如果项目是全新的、甲方又明确要求跨平台,那就直接考虑OPC UA。OPC DA走COM/DCOM,进程间多了不少玄学问题;OPC UA走TCP,端口和证书都干净得多。只是OPC UA的C#客户端通常要引额外的NuGet包,代码模型和OPCAutomation.dll差别不小,老项目没必要为它推倒重来。

2.2 环境准备:添加COM引用、平台位数与免费的OPC服务器

开发机上先要装OPC Core Components,里面包含了OPCAutomation.dll需要的运行环境。很多OPC服务器安装包会带这个组件,但为了保险,单独装一次更稳。装完之后,在Visual Studio里建一个Winform或控制台项目,右键引用 → 添加COM引用,在列表里找到“OPC Automation 2.0”,添加进来,系统会自动生成Interop.OPCAutomation.dll包装类。如果列表里看不到,检查两件事:第一,OPC Core Components是否装好;第二,项目目标框架是否兼容COM引用。

平台位数是个特别容易翻车的地方。OPC服务器是独立进程,C#客户端通过COM和它通信。客户机上如果是32位OPC服务器,你的程序就要生成x86;如果两边都是64位,生成x64。常见做法是打开项目属性 → 生成 → 平台目标改成x86,因为工业现场大量老设备驱动还是32位。这个看实际环境,调试的时候先在任务管理器里看OPC服务器进程是32位还是64位,再改程序目标。改完记得把“首选32位”选项一起勾上,不然AnyCPU还是会跑到64位进程里。

测试环境我一般建议先用免费的OPC服务器。常见做法是装一个Matrikon OPC Simulation,它自带仿真标签,不需要真实PLC,适合跑通链路;Kepware也有试用版,但仿真点位和授权限制多一些。刚开始写代码不要直接连西门子或数控机床,先连仿真服务器,把配置、采集、写入都调通,再接真设备。OPC服务器的ProgID可以在安装后用注册表查,运行regedit,到HKEY_LOCAL_MACHINE\SOFTWARE\Classes下搜“OPC”,能找到类似Matrikon.OPC.Simulation这样的字符串,这个值就是Connect的第一个参数。

环境配置还涉及一个容易被忽略的点:客户端和服务器在同一台机器上用localhost,跨机器就必须配DCOM。DCOM配置在第5章详细讲,这里先记住一个原则,能本机调试就不要跨机,跨机调试之前先把两台机器的防火墙和用户权限理顺,不然所有时间都会耗在“连不上”上。

2.3 最小连接与读取代码:5分钟跑通第一个实时值

环境准备好后,用控制台程序就能验证链路。核心代码如下:

using System; using OPCAutomation; class Program { static void Main(string[] args) { // 创建OPC服务器对象,这个类是COM包装过来的 OPCServer server = new OPCServer(); server.Connect("Matrikon.OPC.Simulation", "localhost"); // 添加一个组,组可以理解为一个标签集合的容器 OPCGroup group = server.OPCGroups.Add("DemoGroup"); // 添加标签,第二个参数是客户端句柄,后续读写用 OPCItem item = group.OPCItems.AddItem("Bucket Brigade.Int4", 1); // 同步读一次当前值 object value = item.Value; Console.WriteLine("当前值: " + value); server.Disconnect(); } }

这段代码做了四件事:连接服务器、添加组、添加标签、读一次值。关键的参数是Connect的服务器ProgID和主机名。“Matrikon.OPC.Simulation”是Matrikon仿真服务器的ProgID,大小写和点号不能写错;连Kepware时要把这部分换成本机已安装的服务器ProgID。localhost可以换成IP,但跨机器读值要配DCOM权限,放到第5章讲。

AddItem的第二个参数是给这个标签分配的客户端句柄,后续批量读写时用这个句柄在数组里定位标签,而不是每次都对字符串。Value属性返回的是object,实际类型跟着服务器下来,可能是short、int、float、string或bool,所以拿值后不要直接强转int,先看监控值。运行程序前,要把Matrikon仿真服务器打开,标签“Bucket Brigade.Int4”是一个不断变化的整数,如果显示0或不动,多半是标签名写错了。

组还有一个UpdateRate属性,单位是毫秒,决定服务器按什么频率刷新这个组的缓存。默认值一般是1000,也就是一秒一次。如果现场要求200毫秒刷一次,可以在Add之后设置group.UpdateRate = 200。但这个值不是越小越好,OPC服务器底层还要和PLC通信,调太小会加大现场总线负载,数据没快多少,设备先报警了。同步读不受UpdateRate限制,SyncRead强制穿透到设备;用Item.Value这种读法才会走组缓存。

3. 读取写入与界面刷新:用C#把PLC、传感器、数控机床数据搬到Winform

3.1 同时读多个标签:SyncRead批量读取与类型转换

实际采集场景不会只读一个标签。一个产线可能有几十台设备,每台设备有温度、转速、扭矩、状态码五六个变量,逐个读Value在大点数场景下很慢,而且会和UI线程打架。OPC组提供了一个批量读取接口SyncRead,一次调用带回一组标签的值。示例代码如下:

using OPCAutomation; // 假设已经在server上建好group并添加了3个标签 int[] handleList = new int[] { 1, 2, 3 }; int count = handleList.Length; Array values; Array errors; // 从设备上读取这3个标签的实时值 group.SyncRead((short)OPCDataSource.OPCDevice, count, handleList, out values, out errors); for (int i = 0; i < count; i++) { int clientHandle = handleList[i]; string tagName = group.OPCItems.Item(clientHandle).ItemID; Console.WriteLine($"{tagName}: {values.GetValue(i)}"); }

这里SyncRead的第一个参数指定数据来源,OPCDataSource.OPCDevice表示强制从设备读取实时值,而不是读服务器的缓存值。第二个参数是要读的标签数量,第三个参数是句柄数组。句柄和标签的对应关系在AddItem时就确定了,所以批量读之前要把所有标签先Add到组里。返回的values和errors都是数组,errors[i]为0表示这个标签读取成功。这个接口避开了逐个读取的多次COM调用,数据点数量多的时候性能差距非常明显。

类型转换是这个环节最容易踩雷的地方。PLC过来的整数在OPC服务器里常被表示为short、ushort、int、uint、float几种,同是一个温度值,西门子S7组的标签和Modbus设备的标签返回类型可能完全不同。我一般不直接做(int)强转,而是用Convert类先转。比如:

object raw = values.GetValue(i); if (raw == null) continue; float num = Convert.ToSingle(raw);

Convert处理数值类型更安全,但遇到字符串类型会抛异常,所以工业现场更稳的做法是判断TypeCode后再处理。你可以在标签命名阶段就统一约定,所有模拟量标签在服务器端就配成float,工程量写在Excel里随项目交付,能省掉不知道多少莫名其妙的问题。浏览服务器里的标签时,可以用OPCServer的浏览器接口遍历,也可以直接用OPC客户端工具把标签名导出来,再填进配置表,不要手工敲,容易漏字母。

3.2 往设备写数据:让OPC反向控制PLC

OPC不止用在上位机采集数据,也可以做反向写操作。最常见的场景是从Winform界面上写一个启动命令、复位信号或者目标温度给PLC。OPCItem提供了Write方法,写法如下:

// 找到要写的标签,前面AddItem时已经加了 OPCItem cmdItem = group.OPCItems.Item(4); cmdItem.Write(true); // 写入一个布尔启动信号 // 或者写浮点数值 OPCItem tempItem = group.OPCItems.Item(5); tempItem.Write(120.5f);

Write方法的参数类型要和服务器内部类型匹配,写字符串、布尔和浮点的行为不太一样。现场调试时如果写失败,要优先看服务器端的错误日志而不是代码。很多人把Write当作同步方法,但OPC服务器和PLC之间的通信有延时,写了信号后立刻回读可能会读到旧值,需要隔几个扫描周期再判断状态。写命令前最好在界面上做二次确认,因为这种操作一旦出错,直接作用在真实设备上,没有后悔药。

还有一个细节值得说:某些OPC服务器对写权限有单独配置,标签默认可能是只读的。在Kepware里,每个标签的访问模式可以设成Read/Write或Read Only,如果上位机写不进去,先去检查这个。跟西门子S7做写操作时,地址区域和数据长度必须严格匹配,DB块地址写错一位,服务器不会报错,但PLC侧就是不执行。这时候手头有一份设备点表会方便很多,没有点表就只能用别人写好的工程源码反推,非常痛苦。

3.3 界面刷新:Winform状态栏与进度条的正确姿势

上位机界面上要显示采集值,常规做法是在Winform里放一个Timer或者用后台线程更新Label。但这里有个老生常谈的坑:不要在后台线程里直接操作控件。控制台程序无所谓,Winform里跨线程访问控件会抛InvalidOperationException,或者偶尔界面卡死。正确做法是控件本身就在UI线程更新,或者用Control.BeginInvoke封送。

常见做法是定义一个委托方法,在后台采集线程里把数据封送回来。比如:

private void UpdateValue(string tagName, object value) { if (this.lblValue.InvokeRequired) { this.lblValue.BeginInvoke(new Action<string, object>(UpdateValue), tagName, value); return; } this.lblValue.Text = $"{tagName}: {value}"; }

这个方法在后台线程里调用时,BeginInvoke会把方法调用投递到UI线程执行,不阻塞后台采集循环。Winform里更新状态栏和进度条也是一样的套路,状态栏显示连接状态,进度条显示当天产量或者采集卡顿率,全部通过InvokeRequired判断。这个写法虽然啰嗦,但稳定,比用Timer去读一个共享变量要可靠得多,因为共享变量在多线程下还要加锁,锁不好就卡顿。

除了BeginInvoke,还有一个更轻的方案:用Timer控件,每500毫秒去读一次采集器里缓存的最新值,然后刷新界面。这个方案的前提是采集器已经把数据放到一个线程安全的属性里。但要注意,System.Windows.Forms.Timer是委托到UI线程执行的,它本身不干活,只做显示;System.Threading.Timer是后台线程,用它更新控件时还是要走Invoke。把采集和显示分开,是这套工程源码最核心的设计原则。

4. 一个C#工程源码怎么组织:连接管理、采集线程与断线重连

4.1 工程源码的模块划分:连接器、采集器、界面三层

标题里提到的“一个C#工程源码”,我按自己做上位机项目的习惯把它拆成很清晰的三层,照着这个结构组织代码,后面维护会省很多事。第一层是连接器,负责OPC服务器的连接、断开和重连,把OPCAutomation.dll的COM操作全部封装在一个类里,界面层不碰这些细节。第二层是采集器,负责定时轮询、标签配置、数据缓存和事件通知。第三层是界面层,只管显示和交互。

常见的文件划分是这样:App.config存放OPC服务器ProgID、主机名、标签列表;OpcConnector.cs封装连接与重连;OpcCollector.cs封装采集循环和数据变更事件;MainForm.cs只管把数据画到界面上。标签列表建议用配置表而不是硬编码在代码里,因为项目交付后经常要加一个点位,如果硬编码,现场改要重新编译发布,很被动。

public class OpcConnector { public OPCServer Server { get; private set; } public OPCGroup Group { get; private set; } public void Connect(string progId, string host) { if (Server == null) Server = new OPCServer(); // 先判断当前是否已经连接,避免重复连接 int state = Server.ServerState; if (state != (int)OPCServerState.OPCRunning) { Server.Connect(progId, host); } Group = Server.OPCGroups.Add("MainGroup"); Group.UpdateRate = 500; } public void Disconnect() { if (Server != null) { Server.Disconnect(); Server = null; Group = null; } } }

这个类把OPC连接细节封装起来,外部调用者只需要Connect和Disconnect两个方法,不需要知道OPCServer对象的生命周期。ServerState是一个枚举值,OPCRunning表示服务器正常,这样做可以避免每次重连时创建一堆重复对象。工程源码里还有App.config,大概长这样:

<appSettings> <add key="OpcProgId" value="Matrikon.OPC.Simulation" /> <add key="OpcHost" value="localhost" /> <add key="PollIntervalMs" value="500" /> </appSettings>

标签列表我习惯放在一个单独的Tags.csv里,格式是三列:ItemId、ClientHandle、DataType。程序启动时读这个文件,逐个AddItem。这样做的好处是现场加标签不用动代码,改完CSV重启程序就行。

4.2 数据采集线程:后台轮询不卡界面的线程模型

采集线程是工控上位机的心脏。我见过不少翻车案例,把读取写在UI按钮事件里,点一下读一次,界面就卡住几秒,最后整个程序假死。正确做法是启动一个后台线程或者Task,按照设定周期循环读取数据,侦测到设备状态变化立刻通知界面。

private async Task PollLoopAsync(CancellationToken token) { while (!token.IsCancellationRequested) { try { Array values; Array errors; group.SyncRead((short)OPCDataSource.OPCDevice, handles.Length, handles, out values, out errors); OnDataUpdated?.Invoke(values, errors); } catch (Exception ex) { OnError?.Invoke(ex); await Task.Delay(1000, token); } await Task.Delay(PollIntervalMs, token); } }

这个循环用CancellationToken控制退出,不采集的时候线程不空转,每轮之间用Task.Delay让出CPU。PollIntervalMs按实际需求设定,设备变化要求快的用200到500毫秒,一般状态监测用1秒就够了,没必要一味调小,因为OPC服务器和PLC扫描周期本身有限制,调太小只会增加COM调用压力。

SyncRead在OPC服务器没有响应时可能抛出COM异常,所以catch里面必须做处理,否则后台线程直接挂掉,程序表现为“不采集了但界面还好好的”,线上排查很痛苦。把异常通过OnError事件发给界面层记录日志,同时延迟1秒重试,是一种简单实用的降级策略。在实际工程里,OnError事件里还会记录当前重试次数,连续超过10次就触发重连流程,这样比每次出错都重连要稳。

4.3 数据缓存、历史值与断线重连

采集到的数据如果只用来刷新界面,断电重启后没有任何历史依据,甲方来问“刚才几号机发生了什么”,你只能干瞪眼。常见做法是在采集器里维护一个队列或者内存表,保存最近一段时间的数据,同时定期写CSV或者SQLite。写入频率不用太高,每秒一条,一天的文本量也不大,比事后找厂家要数据强太多。

public class DataCache { private readonly ConcurrentQueue<DeviceRecord> _queue = new ConcurrentQueue<DeviceRecord>(); public void Add(DeviceRecord record) { _queue.Enqueue(record); // 只保留最近10分钟的数据 while (_queue.Count > 600) { _queue.TryDequeue(out _); } } }

ConcurrentQueue是线程安全的队列,采集线程往里面写,界面线程或者写盘线程从里面读,谁都不卡谁。历史值写CSV时注意带上时间戳和标签名,文件按天滚动,文件名里放日期。这样现场排障的时候,能精确到秒地回看数据。

断线重连是另外一个必修课。OPC服务器重启、网络抖动、DCOM会话超时都会导致客户端失去连接。采集线程在SyncRead抛出异常时不能干等着,要尝试重新连接。重连带退避:第一次等待1秒,第二次2秒,最多30秒,防止服务器还没起来时客户端疯狂重连把机器拖垮。

private async Task TryReconnectAsync() { var delay = TimeSpan.FromSeconds(1); while (!_cts.IsCancellationRequested) { try { _connector.Disconnect(); _connector.Connect(_progId, _host); ConfigureTags(TagConfigList); return; } catch { await Task.Delay(delay); if (delay < TimeSpan.FromSeconds(30)) delay = delay.Add(TimeSpan.FromSeconds(2)); } } }

重连成功后要重新添加Group和Item,因为旧的COM对象可能已经失效。这里我踩过坑,第一次写重连逻辑时只调了Connect没重新建Group,结果连接看起来正常但读不到值,黑匣子一样。断线重连日志一定要写清楚时间点和重连次数,现场排障全靠它。日志一种简单写法是写到本地txt,带时间戳,别只输出到调试窗口,现场没人看那个。

5. OPCAutomation.dll实战避坑:连接、类型、线程与权限的常见问题

5.1 连接与权限问题:DCOM配置、远程连接失败、服务未启动

现象一:本地连接仿真服务器成功,换成远程设备就报“拒绝访问”或“服务器未找到”。

原因是OPC DA依赖DCOM做远程通信,DCOM默认权限对普通用户不友好,尤其是Windows防火墙开启后,远程连接基本都会被拦掉。解决方法是客户端和服务器机器上都运行dcomcnfg,找到OPC服务器相关的DCOM组件,比如OpcEnum和Kepware的ExProcess,在安全选项卡里添加允许启动、激活、访问的用户,并把Everyone加进去做临时验证。防火墙放行TCP 135端口和程序对应的端口。这个配置是玄学最多的环节,不同Windows版本界面还不一样,我一般是先关闭双方防火墙验证是否权限问题,再逐个放行。

现象二:运行程序时提示找不到OPCAutomation.dll,或者COM类未注册。

原因是OPC Core Components没装,或者程序平台位数和OPC服务器不一致,C#项目用AnyCPU编译时可能在64位进程里找32位COM组件。解决方法是先安装OPC Core Components;然后把项目平台目标改为和OPC服务器一致。如果还不行,把bin目录下的Interop.OPCAutomation.dll删掉,重新在VS里添加COM引用,让VS重新生成包装类。不要手动拷贝DLL到System32,那样反而会引发版本冲突。

现象三:Kepware或西门子OPC服务器连接报“没有可用的服务器”。

原因是OPC服务器服务没有启动,或者运行在这个账号下没有访问权限。解决方法是到服务管理里找到对应的服务,比如KepwareEx,确认它是运行状态。西门子SIMATIC NET的OPC服务器还要检查授权和PG/PC接口设定,这个和具体软件版本绑定,查官方错误日志比猜快。在C#侧,Connect这个方法本身不抛出异常,但ServerState会停在OPCFailed状态,所以连接之后要主动检查一下状态,别急着Read。

5.2 数据与线程问题:类型转换、UI卡死和异步回调异常

现象一:读到值一直是0,或类型突然变成Int16。

原因是OPC标签配置的数据类型和设备实际数据类型不匹配。比如设备输出的是UInt16,服务器端配成Int16,符号位吃掉一半范围,显示就乱了。解决方法是先在OPC服务器客户端工具里看原始类型,再统一到C#侧的转换逻辑。我的习惯是采集层用一个字典维护每个标签的物理含义和数据类型,从服务器读到的object先根据字典做Convert,而不是无脑转float。这个字典要随着工程源码一起交付,现场加标签时同步更新。

现象二:Winform界面点击查询后卡住,转圈圈几秒才恢复。

原因是把SyncRead写在了UI线程里,OPC服务器响应一慢,整个消息循环被堵住。解决方法是把读取放进后台任务。如果只是临时验证,可以用Task.Run包一下;长期工程按第4章的采集线程模型来。这里补一条代码,Task.Run里读到的数据必须封送回来更新UI:

Task.Run(() => { Array values; Array errors; group.SyncRead((short)OPCDataSource.OPCDevice, handles.Length, handles, out values, out errors); BeginInvoke(new Action(() => { // 在这里更新界面控件 })); });

现象三:用DataChange事件监听数据变化,偶尔程序直接崩溃。

原因是OPC组的数据变化回调是在COM的线程池里执行的,回调方法里直接操作UI控件或抛出未捕获异常,就会触发程序崩溃。解决方法是回调方法最外层套try/catch,回调里只做数据记录或通过BeginInvoke更新UI。还要注意事件源可能在断线时失效,重连后必须重新挂上事件。这个坑我在第4章重连逻辑里吃过亏,后来把事件挂接和标签初始化绑在一起,重连成功后统一重建,才算消停。

另外补充一个容易被忽略的现象:程序退出时报COM异常,或者进程无法正常结束。原因是OPCServer对象没有被释放,COM引用计数一直没归零,OPC服务器进程还认为客户端在线。解决方法是退出前调用Disconnect,再把OPCServer置为null,最后调用GC.Collect加GC.WaitForPendingFinalizers,强制清理COM RCW。如果直接杀进程,服务器端会等一段时间才释放会话,在这一段时间里重启上位机会连不上。

6. 进阶技巧:用异步封装OPC读写,给OPC UA迁移留一条退路

6.1 用async/await把采集读操作封装成任务

前面采集线程用的是SyncRead同步接口,但在Winform里事件驱动的地方,用async/await可以少敲很多封送代码。把同步读取包到Task里,await之后直接更新界面,编译器帮你把上下文切回UI线程,代码可读性提升一大截。

private async Task RefreshValuesAsync() { var result = await Task.Run(() => { Array values; Array errors; group.SyncRead((short)OPCDataSource.OPCDevice, handles.Length, handles, out values, out errors); return new object[] { values, errors }; }); UpdateGrid(result[0] as Array, result[1] as Array); }

注意await Task.Run之后代码是否回到UI线程,取决于调用这个方法本身是不是在UI线程上下文里。如果采集循环是独立后台任务,就不要依赖这个糖,还是用回调或者ConcurrentQueue。这个技巧适合界面上的“手动刷新”按钮,点了之后异步读一次,不卡界面。

6.2 给OPC UA迁移留退路:接口抽象与标签表

OPC UA现在越来越普及,新项目里我会建议直接把采集接口抽象成它,但如果你手里正好是老项目,不需要推倒重来。写一个IDataSource接口,把Connect、Read、Write三个方法定义好,然后分别实现OpcDaSource和OpcUaSource。标签表单独一个CSV或者数据库,迁移时只需要改实现类,标签名基本不变。这样甲方以后提OPC UA,你只用加一个类,而不是把工程源码翻个底朝天。

这个抽象还有一个好处:测试的时候可以写一个假的设备源,随机生成数据,界面开发和设备调试并行,不用等现场做完了才能写界面。我大概花半天时间就可以在原有OPC采集代码外面套上这层接口,之后每次交付新项目,省掉的都是现场联调时间。

6.3 结尾

我自己的习惯是,每次交付上位机都保留一个命令行小工具,只做一件事:连接OPC服务器、读指定标签、循环打印,用来验证设备和网络链路。这个工具调试时是救命稻草,等于是给黑匣子开了一扇窗。做完这套C#操作OPC的方案后,我把总结的经验都固化在开头那个连接器类里,后来的项目基本就是改标签表和界面,采集模块很少再动。希望这一篇能把你在OPCAutomation.dll上的弯路子铺平,真正把工程源码稳定跑起来,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询