WinCC和Excel做数据交互,用全脚本的方式实现按条件自动查询,把Excel里的数据带回WinCC画面——这套做法我在好几个现场项目里反复用过。说直白点,就是WinCC跑着跑着,操作员在画面里输入一个条件(比如批次号、工号、物料编码),点一下按钮,脚本自动去Excel表里把对应行找出来,把需要的列写到WinCC变量里,再显示到画面上。整个过程不需要装任何第三方控件,不碰WinCC的ODK,也不依赖DataMonitor,完全靠WinCC自带的脚本能力就能搞定。
这篇文章就是给那些被“WinCC怎么读Excel”困扰的同行准备的。不管你用的是WinCC 7.x还是8.x,只要系统里装了Excel,按着下面的思路走,基本都能落地。我会把方案选型、表结构设计、脚本逐行拆解、画面挂接、触发配置,以及我在现场踩过的坑全部摊开讲,属于那种“拿过去就能抄作业”的实操记录。
1. 需求场景与方案选型
1.1 先弄清楚:Excel数据从哪来、要到哪里去
在做任何脚本之前,先别急着写代码,把数据流向想清楚比什么都重要。我见过太多人上来就复制一段“打开Excel、读取单元格”的代码,结果连“查哪个表、查哪列、查完放哪”都没搞明白,最后脚本跑不通也不知道改哪里。
这类需求在工业现场其实非常普遍。典型场景有这么几种:一是配方管理,工艺工程师在Excel里维护配方表,操作员在WinCC画面输入配方号,脚本自动把对应配方的温度、压力、时间等参数加载出来,再下发到PLC;二是批次追溯,扫码枪扫到一个条码,WinCC拿条码去Excel台账里查批次对应的生产日期、操作员、设备编号;三是临时查询,车间主任想看看某台设备某天的运行记录,直接在画面里输入设备号和日期查Excel导出的报表。
这些场景的共同点是:Excel是“数据源”,WinCC是“查询入口和展示界面”,脚本是中间的搬运工。数据不一定要写回Excel,多数时候只是从Excel读出来,填到WinCC的内部变量或者外部变量里,给画面显示,给PLC下发。
有一点要说清楚:如果数据量大到几千上万行,或者需要多人同时访问、频繁并发读写,那老老实实换数据库(SQL Server / MySQL)才是正路。Excel适合的数据量级是几千行以内、读取频率不高、单机使用的场景。超过这个范围,Excel会卡到你怀疑人生,WinCC脚本也会因为长时间占用COM对象导致系统不稳定。
1.2 为什么选VBScript + ADO/Excel对象这套组合
WinCC支持两种脚本语言:C脚本和VBScript。做Excel交互,我强烈建议用VBScript,别碰C脚本。原因很简单:VBScript天生就是为COM编程设计的,操作Excel.Application、ADODB.Connection这些组件非常顺手,代码写起来直观;C脚本虽然也能调COM,但那个语法绕来绕去,声明、转型、内存管理一堆事,是给自己找罪受。
具体读取Excel又有两条路:
第一条路是直接创建Excel.Application对象,用Excel自带的Workbooks.Open去打开文件,然后像操作Excel界面一样遍历单元格。这条路的好处是思路简单,新手好理解,还能顺便做写操作、改格式。坏处是必须安装Excel,而且每次创建和销毁Excel进程都比较重,连续频繁调用容易留下EXCEL.EXE进程残留。
第二条路是用ADO(ActiveX Data Objects)把Excel当成一个数据库来查,通过SQL语句的WHERE条件筛选数据。这条路的好处是查询效率高、语法简洁、不用真正打开Excel界面,最关键的是它天然支持“按条件查询”这个语义——标题里说的“根据条件自动查询”,用ADO是再合适不过的。坏处是只能读、写起来麻烦,对Excel文件的结构要求比较严格。
我的建议是:如果你只是做“按条件查询并显示”,优先用ADO方案;如果后续还要把WinCC的数据写回Excel,或者需要处理复杂的单元格格式,那就用Excel对象方案。这篇文章里两种我都会给完整代码。
1.3 手动查询和自动查询,触发方式怎么定
标题里“自动查询”这个词,不同的人理解不完全一样。结合我在现场接触的需求,其实可以拆成两种触发形态。
第一种是“操作员手动触发”:画面里放一个按钮、一个输入框,操作员输完条件点一下按钮,脚本执行查询。这种触发方式用WinCC的按钮事件就行,打开按钮的“事件”选项卡,选择VBS动作,把你的查询脚本扔进去。
第二种是“系统自动触发”:不需要人点,由某个条件变化自动引发查询。比如PLC里有个批次号变量,每到一个新批次号就变一次,WinCC检测到这个变量变化就自动去Excel查对应的配方参数。这种用WinCC的全局脚本(Global Script)配合变量触发器来实现,把脚本挂到某个变量的“变化触发器”上,变量一变,脚本自动执行。也可以用定时触发器,比如每30秒轮询一次某个PLC变量,为满足条件时才去查Excel。
我在实际项目里两种都会用到。手动触发适合低频查询,自动触发适合需要实时跟进的场景。后面第4章我会把这两种触发方式的配置步骤都写出来。
2. Excel数据表设计与环境准备
2.1 表格结构设计的三条铁律
我发现很多人脚本写好了,但Excel表随手建,最后查不出数据,问题全出在表结构上。这里有三条铁律,都是拿教训换回来的。
第一,第一行必须是表头。不管是ADO方案还是Excel对象方案,默认都把第一行当字段名。ADO的HDR=YES表示第一行是字段名,Excel对象遍历时也习惯从第二行开始找数据。如果第一行不是表头,ADO查询出来的字段名会变成F1、F2这种鬼名字,你的SQL判断条件根本没法写。
第二,一个Sheet只放一张表。别在同一个Sheet里放什么“注意说明”、“修订记录”、“备注”,更别搞合并单元格。ADO查Excel的机制是把整个Sheet当作一个二维表,中间混入无关数据,查询结果会变得非常难处理。Sheet的命名也建议规范化,比如Sheet1就叫“Sheet1”,或者改成“参数表”这种简单的名字,反正你在SQL里要写这个Sheet名。
第三,数据类型尽量统一。Excel单元格本身没强类型,但ADO查询时有类型推断。比如一列里面大部分是数字,中间插入一个文本值,ADO可能直接把整列当作文本,导致数字条件匹配不上。解决办法是保持同一列格式一致,不要数字文本混着来。日期列更要注意,Excel里面存的是日期序列号,直接拿字符串去匹配很容易翻车,后面排错章节我会专门讲。
注意:如果条件查询的字段是“批次号”这种常以0开头的编号,请在Excel里把这一列的单元格格式设为“文本”,或者录入时前面加英文单引号。否则Excel会自作聪明地把“00123”变成“123”,到时候查死都查不到。
2.2 文件路径、权限和Office位数检查
Excel文件放哪里,是个看似不起眼但特别坑的问题。首先,文件路径不要放在C盘系统目录下(比如C:\Program Files),WinCC运行系统(RT)启动时权限很敏感,目录读写权限不够就会报“拒绝访问”。建议放到D盘或者项目目录下的固定子目录,比如D:\WinCC_Data\param.xlsx,路径里尽量不要有中文和空格,减少编码问题。
其次,WinCC运行的时候,那个Excel文件不能被另一个人用Excel打开着。哪怕是WPS预览模式也不行,文件一旦被独占锁定,脚本打开就会报“文件正被使用”。我在现场就直接遇到过:工艺工程师一边改参数表一边盯着画面看,结果参数一改保存,WinCC这边查询全部失败。最后定的规矩是,Excel参数表只在停机和维护时段修改,或者做好台账,修改完立即关闭。
最坑的一个检查项是Office位数。WinCC软件本身是32位进程,它创建的VBS运行环境也是32位的。如果你电脑上装的是64位Office,那么在WinCC脚本里执行CreateObject("Excel.Application")十有八九会报“ActiveX部件不能创建对象”,原因是32位进程调不了64位COM组件。解决办法就三个:装32位Office、改用ADO方案(如果装了32位ACE驱动)、或者用DCOM配置做跨位数适配。这里面最省事的其实是“装32位Office”,但很多现场Office都是公司统一装的64位,改不了,那就老老实实走ADO。
2.3 两种读取方案的选型对照
为了让你一眼就能决定用哪条路,我整理了一张对照表,都是我实测下来的体感:
| 维度 | Excel对象方案 | ADO方案 |
|---|---|---|
| 前提条件 | 必须安装Excel(32位) | 装ACE驱动(32位),不依赖Excel进程 |
| 查询效率 | 遍历单元格,几千行明显变慢 | SQL查询,几万行也快 |
| 代码复杂度 | 简单直观,适合入门 | 稍微抽象,需要懂一点SQL |
| 写回Excel | 方便,直接改Cell.Value | 很麻烦,不推荐 |
| 处理单格格式 | 可以,能改字体颜色等 | 不行,只关心数据 |
| 按条件查询语义 | 需要自己写循环判断 | 原生支持WHERE条件 |
| 资源占用 | 每次创建/销毁Excel进程 | 短连接,开销小,残留少 |
如果你看完还在犹豫,我再给你一个无脑判断方法:你只是想“按条件查出几行数据显示到WinCC”,无脑选ADO;你需要“打开Excel把结果写进去并保存”,无脑选Excel对象方案。两条路不冲突,项目里可以混合用。
3. 核心脚本实现:全流程代码拆解
3.1 方案一:Excel对象遍历查询(适合入门)
先上Excel对象方案的VBScript代码。这个方案的核心逻辑是:创建Excel对象、打开工作簿、定位到工作表、从第二行开始逐行比对条件、找到后读取结果写WinCC变量。
Dim objExcel, objWorkbook, objSheet Dim iRow, iMaxRow Dim strCondition, strResult ' 从WinCC变量读取查询条件 strCondition = HMIRuntime.Tags("QueryCondition").Read ' 创建Excel对象 Set objExcel = CreateObject("Excel.Application") objExcel.Visible = False objExcel.DisplayAlerts = False ' 打开工作簿,路径根据实际情况修改 Set objWorkbook = objExcel.Workbooks.Open("D:\WinCC_Data\param.xlsx") Set objSheet = objWorkbook.Worksheets("Sheet1") ' 获取数据区域的最后一行的行号 ' 1表示第一列,xlUp表示从下往上找第一个非空单元格 iMaxRow = objSheet.Cells(objSheet.Rows.Count, 1).End(-4162).Row ' 默认清除结果,防止查询不到时显示旧数据 HMIRuntime.Tags("QueryResult").Write "" ' 从第2行开始遍历,第1行是表头 For iRow = 2 To iMaxRow If Trim(CStr(objSheet.Cells(iRow, 1).Value)) = Trim(strCondition) Then ' 找到匹配行,读取第2列和第3列,写入WinCC变量 HMIRuntime.Tags("QueryResult").Write CStr(objSheet.Cells(iRow, 2).Value) HMIRuntime.Tags("QueryResult2").Write CStr(objSheet.Cells(iRow, 3).Value) Exit For End If Next ' 关闭工作簿,不保存修改 objWorkbook.Close False objExcel.Quit Set objSheet = Nothing Set objWorkbook = Nothing Set objExcel = Nothing这段代码里有两个细节值得单独抠一下。
一个是End(-4162)这个魔法数字。-4162是xlUp在VBScript中的常量值,表示从当前行向上查找第一个非空单元格。如果你写成End(xlUp)在WinCC的VBS环境里会报错,因为常量没定义,所以要写数字。objSheet.Rows.Count在Excel 2007以上版本是1048576,也就是说从最后一行往上找,找到的第一个非空单元格就是实际数据区的最后一行了。这个写法比UsedRange.Rows.Count靠谱得多。我当初用UsedRange的版本,表格里凡是点过格式、后来删掉内容的行都会被算进去,导致循环遍历一些空行,速度变慢不说还可能误判。
另一个是Exit For。找到第一条匹配的数据之后立刻跳出循环,这是常识,但我在帮别人审脚本时真的见过有人忘了写,结果匹配到多行时做了多次写操作,虽然最终值一样,但白白浪费了查询时间。如果逻辑是“查找到最后一条匹配记录”,那就不应该Exit For,而应该让循环跑完。这种细节完全取决于业务规则。
3.2 方案二:ADO加SQL条件查询(生产环境推荐)
再上ADO方案的代码。这个方案的核心是用SQL语句去查Excel,读取速度、代码简洁度都碾压方案一。
Dim objConn, objRS Dim strConn, strSQL Dim strCondition, strResult ' 从WinCC变量读取查询条件 strCondition = Trim(CStr(HMIRuntime.Tags("QueryCondition").Read)) ' 构造连接字符串 ' Excel 97-2003 (.xls) 用 Microsoft.Jet.OLEDB.4.0 ' Excel 2007+ (.xlsx) 用 Microsoft.ACE.OLEDB.12.0 strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=D:\WinCC_Data\param.xlsx;" & _ "Extended Properties=""Excel 12.0;HDR=YES;IMEX=1;""" Set objConn = CreateObject("ADODB.Connection") objConn.Open strConn ' SQL查询,[Sheet1$]是Sheet的名字,字段名是表头 ' WHERE条件中拼接查询条件 strSQL = "SELECT * FROM [Sheet1$] WHERE 批次号='" & strCondition & "'" Set objRS = CreateObject("ADODB.Recordset") objRS.Open strSQL, objConn, 1, 1 ' 默认清除结果 HMIRuntime.Tags("QueryResult").Write "" HMIRuntime.Tags("QueryResult2").Write "" ' 判断是否查询到数据 If Not objRS.EOF Then HMIRuntime.Tags("QueryResult").Write CStr(objRS.Fields("日期").Value) HMIRuntime.Tags("QueryResult2").Write CStr(objRS.Fields("操作员").Value) End If objRS.Close objConn.Close Set objRS = Nothing Set objConn = Nothing这个方案的底层逻辑是:ACE驱动把Excel文件映射成一个数据源,[Sheet1$]就是一张表,批次号、日期这些字段名就是表头对应的列名。SQL语句里的WHERE 批次号='xxx'由驱动完成过滤,你拿到的Recordset里已经只有匹配的行了,完全不用自己写循环。
连接字符串里的几个参数挨个解释下:HDR=YES表示第一行是表头,如果你是那种前几行写了项目名称没有表头的表,这个参数会导致第一行被当成字段名而查不到数据;IMEX=1表示混合数据类型列按文本读取,这个参数能减少前面提到的类型不一致问题,但也不是万能药,最根本的还是表结构要规范;Data Source后面直接跟文件路径,如果路径有中文,建议先用Chr(34)拼接引号处理。
注意:SQL语句里拼接字符串,要小心条件值里带单引号的情况,比如批次号里真的包含
'字符,SQL会直接报语法错误。严谨的做法是用Replace(strCondition, "'", "''")把单引号转义一下。这种“SQL注入”在工控场景里虽然不像Web端那么致命,但如果条件值是从现场条形码读进来的,谁知道条码里会不会有奇怪字符呢。
3.3 脚本里的几个关键参数和为什么要这样写
写WinCC VBS的同行应该都熟悉HMIRuntime.Tags("标签名").Read和.Write这套读写方式。但有几个细节值得展开讲,因为踩过坑。
第一,标签不存在时直接报错。如果你在脚本里写HMIRuntime.Tags("A_Tag_That_Not_Exists").Read,运行时会直接抛异常。所以脚本里引用的标签,一定要先在WinCC变量管理里建好,并且变量名大小写要完全一致。VBS对标签名的解析虽然不区分大小写,但建议保持统一,省得后期维护看得眼晕。
第二,Read和Write返回的都是Variant类型。从Excel里读出来的值,最好统一用CStr()转成字符串再写入WinCC变量,避免类型不匹配的问题。尤其有些变量组态成了浮点数,Excel里空单元格读出来是Null或Empty,直接赋值会报类型错误。我在写结果之前先写一个空字符串清空旧值,就是为了防止“这次查询没结果,画面上还挂着上一次的值”,这种残留数据最容易误导操作员。
第三,ADO连接字符串里Extended Properties这一段,在VBS里写的时候需要用双引号转义,所以你会看到""Excel 12.0;HDR=YES;IMEX=1;"""这种“三个引号叠一起”的写法。第一对引号是在VBS里表示字符串字面量,里面的一对引号代表实际字符串中的一个引号。新手抄代码的时候最容易抄错这一段,少了引号或者多了引号,运行时大概率报“不能打开文件”。
3.4 多条件组合查询与结果写回画面
实际项目中“按条件查询”很少只有一个条件。比如要查“某天某个班次的产量”,就得同时匹配日期和班次两个字段。用ADO方案改SQL语句就行:
strSQL = "SELECT * FROM [Sheet1$] WHERE 日期='" & strDate & "' AND 班次='" & strShift & "'"用Excel对象方案,循环里的判断就改成:
If Trim(CStr(objSheet.Cells(iRow, 1).Value)) = strDate And _ Trim(CStr(objSheet.Cells(iRow, 2).Value)) = strShift Then ' 匹配成功 End If除了多条件,还有一种常见需求是“模糊查询”。比如操作员输入“A1”想查所有以A1开头的批次号。ADO方案把=改成LIKE就行:
strSQL = "SELECT * FROM [Sheet1$] WHERE 批次号 LIKE '" & strCondition & "%'"Excel对象方案就用Left()或InStr()判断。
写入结果的时候,如果查出来有多列数据要显示,要么像示例代码一样写多个WinCC标签,一个萝卜一个坑;要么把结果拼接成一个字符串,写到单个标签里,画面用文本对象显示。我个人建议前一种,写多个标签,画面布局更灵活,比如某些列要做数字格式、某些列要做颜色区分。拼接字符串虽然省标签,但后期维护画面内容和脚本的对应关系会比较痛苦。
4. 把脚本接进WinCC:画面集成与触发配置
4.1 IO域、按钮与内部变量的搭建
脚本本身再漂亮,不接进画面等于白写。在WinCC图形编辑器里,搭建查询界面一般需要这几样东西:一个IO域用来输入查询条件,一个IO域(或文本对象)用来显示查询结果,一个按钮用来触发查询。如果有多个结果显示,就多放几个IO域。
画面组态前,先到变量管理里建好内部变量(内部变量就可以,不一定要外部变量)。内部变量是WinCC运行系统内部的存储单元,不连PLC,专门干这种脚本中间数据的活。我一般这样建:
| 变量名 | 数据类型 | 用途 |
|---|---|---|
| QueryCondition | 文本变量 | 保存操作员输入的查询条件 |
| QueryResult | 文本变量 | 第一个查询结果 |
| QueryResult2 | 文本变量 | 第二个查询结果 |
| QueryFlag | 二进制变量 | 用作查询状态位,可接PLC触发 |
变量建好后回到画面编辑器。操作员输入条件用的IO域,把“过程变量”关联到QueryCondition,这样操作员在画面上输入的内容会实时写入这个变量。查询结果显示用的IO域,把“过程变量”关联到QueryResult,脚本里写入变量的值会自动显示在画面上。
4.2 按钮事件脚本挂载步骤
按钮触发是最简单的挂载方式。在画面上放一个按钮控件,双击打开属性对话框,切到“事件”选项卡。找到“鼠标操作”下的“按下左键”事件,在右侧下拉选“VBA动作”(有的版本显示为“VBS动作”),这时会弹出一个空的脚本编辑器窗口,把你写好的VBS查询脚本粘贴进去,保存编译。
这里有个WinCC版本差异问题。部分版本里事件动作的脚本类型下拉框可能没有“VBA动作”,只有“C动作”。这时候不用慌,在画面对象的事件里选“VBA动作”是WinCC提供的VBS脚本通道,如果确实没有,可以全局脚本编辑器里先建VBS动作,然后画面按钮事件里选择“调用动作”并指向这个VBS动作。
脚本里要注意一个细节:按钮事件脚本里可以直接用HMIRuntime对象,不需要任何额外声明。而如果你在全局脚本编辑器里写的是“项目函数”或“标准函数”,那个环境里的VBS默认没有HMIRuntime的自动引用,需要自己写Set objRT = HMIRuntime或者用Screen前缀。
4.3 定时轮询与变量变化自动触发
自动查询有两类配置方式,我分开说。
定时触发:打开WinCC的“全局脚本”编辑器(Global Script),在左侧“动作”分支上右键新建一个“VBS动作”。在这个动作里写查询逻辑,然后在动作右上角的“触发器”区域右键新建触发器,选“时间”类型,设置周期,比如每30秒执行一次或者每天固定时间执行一次。定时的意义在于周期轮询:如果PLC里有个“查询请求”标志位,操作员或者PLC把它置1,下一次定时到点时脚本发现标志位是1,就去查Excel并把结果写回去。
变量触发:还是全局脚本编辑器,新建VBS动作后在触发器里选“变量”类型,勾选某个变量(比如批次号变量),触发器方向选“变化时”。这样只要批次号一变,WinCC立刻执行脚本查询。这种触发方式非常契合“根据条件自动查询”的语义——不需要人点按钮,数据源一旦更新或者PLC变量一变,查询就自动完成。
定义触发器时建议勾选“过程驱动”,这样WinCC不会做成周期扫描触发器,而是真正由变量事件驱动,响应快、系统负载小。
4.4 进程生命周期管理与释放
这节几乎全是血泪教训。用Excel对象方案时,最怕的就是脚本跑一半报错退出,Excel进程留在后台。表现就是任务管理器里一堆EXCEL.EXE进程,越积越多,最后WinCC运行系统越来越卡,甚至直接提示内存不足。
要根治这个问题,第一是结构上要保证释放,就是代码里的objWorkbook.Close、objExcel.Quit和Set对象为Nothing,一个不能少。第二是写一个异常处理,VBS里用On Error Resume Next配合Err.Number判断,出错时也能走完释放流程。第三是养成习惯,隔一段时间检查一下运行WinCC的那台电脑,任务管理器里有没有异常堆积的EXCEL.EXE。如果发现堆积,除了手动结束进程,还得回去检查脚本哪里出现了异常提前退出。
ADO方案相对好一些,因为它不需要创建Excel进程,但也要注意Recordset和Connection都要Close和置空。另一个容易被忽略的点是:文件路径不要写成相对路径。WinCC运行系统的当前目录不一定是你想要的目录,相对路径会导致偶尔找不到文件,而绝对路径加上统一的文件目录规划,配合维护规范,能省掉大量排错时间。
5. 实战排错:我在现场踩过的坑
5.1 常见问题速查表
这些年帮人排过的WinCC读Excel的故障,汇总成一张速查表,基本覆盖了90%的问题:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 报“ActiveX部件不能创建对象” | Office位数不匹配(64位Office + 32位WinCC) | 换32位Office,或者用ADO方案 |
| 报“文件正被使用” | Excel文件正被其他程序打开 | 关闭所有Excel窗口,确认没有预览窗口锁定 |
| 查询无结果但不报错 | 条件值类型不匹配,或表格里条件列有隐藏空格 | 用Trim()去空格,检查条件列格式是否文本 |
| 报“无法找到可安装的ISAM” | 连接字符串的Extended Properties写错 | 检查Excel版本对应驱动和“Excel 12.0”参数 |
| 画面显示旧数据 | 脚本没有先清空结果变量 | 查询前先Write空字符串 |
| 脚本偶尔报路径找不到 | 使用相对路径 | 换绝对路径并确认目录权限 |
| WinCC运行系统越来越卡 | EXCEL.EXE进程残留堆积 | 查脚本异常退出路径,补释放代码,手动清进程 |
| 中文内容显示乱码 | ADO连接IMEX方式或字段编码错乱 | 文本列统一格式,IMEX=1,检查Excel区域设置 |
5.2 三个值得单独说一说的疑难杂症
第一个是日期条件的坑。我在一个批次追溯项目里遇到过,Excel里日期列明明显示“2024-05-20”,但ADO用WHERE 日期='2024-05-20'去查,死活查不到。后来用SELECT * FROM [Sheet1$]打印全部数据才发现,字段读出来的值变成了“45432”这种数字。这就是Excel日期序列号——Excel内部把日期存成从1900年1月1日起的天数。解决方案是查询前把条件日期转成对应的序列号,或者干脆在Excel里把日期列导成文本格式,和WinCC侧用字符串比较。搞不定日期序列号的,最省事的方法就是:Excel数据源里不让它存日期类型,能导成“文本”就导成文本,“2024-05-20”这种字符串怎么查都不会翻车。
第二个是带格式的“脏表”。有次用户提供的Excel模板里,每一列都有下拉框数据有效性,还有一些隐藏的列。ADO查询时明明能看到数据,但用WHERE条件一过滤就少几条。后来发现是空单元格里藏了不可见字符,VBA输入的空格、换行符都会让字符串匹配失败。最后处理办法是在SQL里用TRIM()函数,或者在写入Excel模板时用数据清洗——把整列重新刷一遍格式,保证没有隐藏字符。
第三个是WinCC运行系统以服务方式启动的场景。有些项目把WinCC RT配置成了Windows服务开机自启,这时候脚本里创建Excel对象会失败,因为服务模式下没有交互式桌面权限给Excel界面用。这个场景最稳妥的方案就是改用ADO,它不需要弹出Excel界面,不需要桌面权限,只要服务账户对文件有读取权限就行。遇到“WinCC开机自动运行、没人登录Windows”的现场,直接上ADO方案,别纠结。
5.3 关于性能的一点补充
如果数据量在1000行以内,两种方案体感差距不大;到5000行以上,Excel对象遍历就开始有明显的延迟了。这不是脚本写得差,而是COM调用的固有开销——Excel对象方案是一格一格地跨进程取数据,每取一个单元格都是一次COM调用。ADO方案则把查询下推到驱动内部执行,而且用SELECT时已经在驱动里完成了行过滤,传回来的数据很少。
所以我的经验法则是:数据量小到中、又需要写回Excel的,用Excel对象方案;但凡数据量可能上千、或者以查询为主的,用ADO方案。性能压测很直观,你要是纠结,就在脚本里加一行计时:
Dim dblStart dblStart = Timer ' 查询逻辑 HMIRuntime.Trace "查询耗时:" & Timer - dblStart & " 秒" & vbCrLf这一行输出到WinCC的调试跟踪,能让你对脚本性能心里有数。
写在最后的经验补充
这篇文章写到这里,核心的代码、配置、避坑基本都覆盖了。最后再分享一个我自己的经验:这种WinCC和Excel交互的脚本,一定不要只在开发环境测通了就交付,要专门让操作员在真实使用场景里跑上几天,看有没有偶发性的查询失败、画面卡顿。我见过太多脚本在开发机上一切正常,到了现场因为文件被占用、因为网络盘的延迟、因为操作员把Excel表改坏了格式,就开始出幺蛾子。所以在交付时,我通常会在Excel文件旁边放一份模板备份,再在脚本里加上日志记录——把每次查询的时间、条件、结果写到一个txt里。这个日志功能用VBS的FileSystemObject做非常简单,却能帮你快速定位很多看起来完全随机的问题。
如果后续想把这套方案做得更完善,可以往三个方向扩展:一是把Excel数据换成SQL数据库,脚本的ADO部分几乎不用改,只改连接字符串和SQL语句就行;二是用WinCC的变量归档把查询结果记录下来,形成历史趋势;三是把这套脚本逻辑封装成全局脚本函数,不同画面复用,减少重复代码。这些都是后话了,先把本文的基础版本跑通,后面的路自然就清楚了。