☰
WinCC通用外部数据库报表模板:C脚本全实现,现场可照抄
2026/9/26 12:46:31 网站建设 项目流程

做WinCC项目的朋友应该都有过这种经历:现场要产量报表、交接班报表、报警统计,第一反应就是用WinCC的自带打印和归档报表功能,真上手才发现又贵又不好改。我在连续做了几个类似项目之后,沉淀了一套“WinCC + 外部数据库报表”的通用模板,核心逻辑全部用C脚本实现,表格样式完全自定义,数据存储也是脚本驱动。换数据库、换报表格式,基本就是改配置的事。这篇文章把这套模板的架构、关键代码和实际踩坑记录完整晒出来,给正在被报表功能折磨的同行一个可以照着抄的方案。

1. 为什么WinCC报表要“绕道”外部数据库,而不是直接用自带报表

1.1 自带报表的“够用但不好用”

WinCC自带的报表模块,在单个小项目、只打印简单过程值趋势的时候,确实能用。但一旦报表需求复杂起来,问题就非常明显。

第一,它的报表布局编辑器操作逻辑和普通办公软件差异很大。想做一张带合并单元格、多级表头、条件高亮的交接班报表,你要在布局里反复调整表格线、文本域和数据列,调试一次布局的时间比写一套脚本还久。

第二,WinCC自带的报表主要面向过程值归档和报警记录。它最擅长的场景是“这台设备在几点几分温度是多少”,而不是“这一班次产量多少、合格率多少、停机多长时间”。后一类业务统计报表,需要在数据库里做聚合、过滤、多表关联,WinCC自带的归档数据模型很难满足。

第三,成本问题。真正好用的WinCC高级报表功能往往要额外选件,例如归档服务器、报表服务器,这些都是按项目授权收费的。而一套按时触发的存储C脚本加外部数据库,几乎不增加硬件和软件成本。

1.2 外部数据库在报表链路里的位置

我的做法是让WinCC回归它最擅长的事情:实时采集、画面监视、报警判断。报表链路里的历史存储和业务统计,全部交给外部数据库。

现场数据从PLC进WinCC变量之后,在需要的时刻(交接班、设备停机、定时触发)由C脚本把数据写入外部数据库。报表系统、Web展示页或者管理部门的第三方软件,直接从这个数据库里取数。WinCC并不直接参与报表展示,只负责“喂数”。

这套分工最大的好处是解耦。WinCC运行库崩溃、画面重装、版本升级,只要数据库里的数据在,历史报表就不会丢。反过来,数据库维护也不会影响WinCC的实时监控。

1.3 这个模板解决的核心问题

市面上很多报表方案都是针对某个具体项目定死的:表名写死在脚本里、字段写死在SQL里、表格样式写死在控件里。换一个现场,所有代码都要重新改。

我这个模板的核心目标就四个字:通用、可配。数据库连接串放配置表,报表表结构用统一约定,字段映射做成配置项,表格展示走HTML模板。接到新项目,改配置、改查询SQL,脚本几乎不动。这也是标题里强调“通用数据库模板”的原因。

2. 通用模板的地基:三层库表结构与配置约定

2.1 主表、明细表、配置表各管什么

数据库结构是整套模板的核心,我习惯分成三层:报表主表、报表明细表、系统配置表。

报表主表记录每一次报表生成的基本信息,相当于“这张报表是谁在什么时间生成的”,表结构大致这样:

CREATE TABLE report_main ( report_id INT IDENTITY(1,1) PRIMARY KEY, report_name NVARCHAR(100), report_type NVARCHAR(50), station_name NVARCHAR(100), shift_no INT, start_time DATETIME, end_time DATETIME, create_time DATETIME DEFAULT GETDATE(), remark NVARCHAR(500) );

报表明细表记录具体的数据行,例如每台设备每小时的产量、合格率、能耗。主表和明细表通过report_id关联。

系统配置表则是模板的“控制台”,所有不能写死的参数都存在这里:

CREATE TABLE sys_config ( config_key NVARCHAR(100) PRIMARY KEY, config_value NVARCHAR(1000), comment NVARCHAR(200) ); INSERT INTO sys_config(config_key, config_value, comment) VALUES ('DB_DRIVER', 'SQLOLEDB', '数据库访问驱动'); INSERT INTO sys_config(config_key, config_value, comment) VALUES ('DB_SERVER', '127.0.0.1', '数据库服务器地址'); INSERT INTO sys_config(config_key, config_value, comment) VALUES ('DB_NAME', 'WinCC_ReportDB', '数据库名称'); INSERT INTO sys_config(config_key, config_value, comment) VALUES ('DB_TAG_PREFIX', 'Line1_', 'WinCC变量前缀');

有了这层配置表,更换数据库或者更换采集变量范围,就不需要动脚本代码了。运行时读配置,动态拼连接串和SQL,这是模板“通用”的基础。

2.2 字段映射做成“可改的”

生产现场的报表字段经常变。今天要加一个“班组长签字栏”,明天要把“设备编号”换成“产线编号”。如果字段是在C脚本里用sprintf拼死的,每次都改脚本、重新编译、重新发布,非常被动。

所以我在明细表之外再加一张字段映射配置表:

CREATE TABLE sys_field_map ( map_id INT IDENTITY PRIMARY KEY, tag_name NVARCHAR(100), -- WinCC变量名 col_name NVARCHAR(100), -- 数据库列名 data_type NVARCHAR(20), -- int/float/string/datetime col_order INT, -- 报表显示顺序 is_key_field INT DEFAULT 0 -- 是否参与报表汇总 ); INSERT INTO sys_field_map(tag_name, col_name, data_type, col_order) VALUES ('Line1_Output', 'output_qty', 'float', 1); INSERT INTO sys_field_map(tag_name, col_name, data_type, col_order) VALUES ('Line1_OKRatio', 'ok_ratio', 'float', 2);

C脚本在运行时读取这张表,拼接插入SQL和查询SQL。这样的设计初期多花一点时间,但后期每次要新增字段,只需要在数据库里插一行配置,WinCC侧不用动。这里的关键经验是:数据类型的解析必须做在脚本基础函数里,int、float、string、datetime四种类型覆盖现场95%场景就够了。

2.3 报表定义也放进数据库

除了字段映射,我还会把“报表模板”本身也放进数据库,而不是放在WinCC画面里。什么意思?就是这张报表叫什么名字、需要查哪几张表、按什么时间范围过滤、按哪个字段分组,全部用配置表描述。

这让报表从“硬编码”变成“数据驱动”。现场想加一种“月度停机统计”,只需要往报表定义表里插入一条记录,再配好查询SQL和HTML表头,C脚本下次启动自动识别新的报表类型,完全不需要重新编译WinCC画面。

不过这里也提醒一点:配置表虽然灵活,但查询SQL一定不能直接拼接用户输入,否则会有注入风险。现场报表的过滤条件建议全部用参数化查询,或者用存储过程传入变量。

3. 全脚本实现的核心:C脚本如何连接、查询、存储外部数据库

3.1 为什么用C脚本而不是VBS

WinCC的画面对象操作,很多人习惯用VBS脚本,因为VBS对象模型方便。但为什么这套模板坚持用C脚本?

首先是性能。C脚本在处理大批量数据、循环遍历时比VBS快很多,尤其是在交接班前后要把几百台设备的数据一次性写入数据库,C脚本的优势非常明显。

其次是环境一致性。VBS脚本在不同WinCC版本、不同语言环境下的对象模型有细微差异,而C脚本的基础语法和执行机制非常稳定,跨版本迁移成本低。

第三是项目要求。很多甲方在技术协议里明确写着“报表模块需要提供C脚本源码”,这时候全C脚本实现就是硬性要求。

但我也要坦白说一句:C脚本不是万能的。它访问ActiveX控件对象的能力很弱,所以模板里我采用了一个折中的思路——数据库访问用C脚本,表格展示走HTML模板,后者根本不是ActiveX,天然绕开了这个短板。

3.2 用中间DLL包住数据库访问

WinCC的C脚本虽然支持调用外部DLL,但直接在脚本里写ODBC或ADO调用会很痛苦,参数复杂、类型转换麻烦,调试一次想摔键盘。

我的做法是先用Visual Studio写一个中间层DLL,把数据库连接的打开、查询、关闭封装成几个简单函数,然后在WinCC C脚本里用#pragma code声明调用。

这里放一段DLL接口声明的示例:

#pragma code("report_framework.dll") int db_open(char* connStr); // 打开数据库连接 int db_query_to_html(char* sql, char* htmlPath); // 查询并输出HTML int db_exec(char* sql); // 执行非查询SQL void db_close(void); // 关闭连接 #pragma code()

DLL里面的实现不外乎就是标准的ADO或者ODBC API,这里不展开。重要的是接口设计原则:让C脚本侧看到的所有参数都是字符串或整数,不出现结构体、数组、指针等复杂类型。这样C脚本声明和调用都非常干净,出问题也好定位。

3.3 三个关键C脚本:连接、写存储、读查询

模板里最核心的是三个C脚本,我分别解释它们的逻辑。

连接数据库的脚本,本质上就是读取配置表,拼出连接串,然后调用DLL打开连接:

char connStr[512]; char server[128], dbname[128], user[128], pass[128]; strcpy(server, GetTagChar("@ServerName")); strcpy(dbname, GetTagChar("@DBName")); strcpy(user, GetTagChar("@DBUser")); strcpy(pass, GetTagChar("@DBPass")); sprintf(connStr, "Provider=SQLOLEDB;Data Source=%s;Initial Catalog=%s;User ID=%s;Password=%s;", server, dbname, user, pass); if (db_open(connStr) == 1) { SetTagBit("@ReportConnected", 1); } else { SetTagBit("@ReportConnected", 0); }

存储数据的脚本,一般在交接班触发变量上升沿或者定时器触发时执行。核心是读取WinCC变量、拼INSERT语句、执行入库:

char sql[1024]; float outputQty, okRatio; outputQty = GetTagFloat("Line1_Output"); okRatio = GetTagFloat("Line1_OKRatio"); sprintf(sql, "INSERT INTO report_detail(report_id, col_name, col_value) " "VALUES(%d, 'output_qty', %f), (%d, 'ok_ratio', %f)", currentReportId, outputQty, currentReportId, okRatio); db_exec(sql); db_close();

查询数据的脚本逻辑刚好反过来:读取报表配置,拼出SELECT语句,调用db_query_to_html输出到HTML文件,最后刷新画面中的WebBrowser控件。

这三个脚本是整套模板的骨架,任何一个项目复制过去,只需要改标签名、表名和业务SQL。

3.4 存储过程与C脚本的分工

一个很容易踩坑的误区是:把所有计算逻辑都写在C脚本里。比如要做“各班次产量对比表”,在C脚本里循环遍历几百条记录,再累加、求平均,不仅脚本长得没法维护,运行性能也差。

正确做法是:复杂聚合交给数据库存储过程,C脚本只负责传参和拿结果。数据库是处理数据聚合的专家,一条GROUP BY语句就能完成的工作,没必要在C脚本里用for循环硬算。

模板里预留了一个统一的存储过程调用方式:

sprintf(sql, "EXEC sp_report_query '%s', %d, '%s', '%s'", reportType, shiftNo, startTimeStr, endTimeStr); db_query_to_html(sql, "D:\\report\\output.html");

这样可以非常优雅地应对不同报表的差异化需求。换一种报表,就是换一个存储过程,脚本完全复用。

4. 全自定义表格:查询结果如何变成一张“好看”的报表

4.1 传统表格控件的两个麻烦

很多WinCC项目想要“表格控件”,第一反应是在画面里塞一个MSFlexGrid之类的ActiveX控件。但实际用起来有两个麻烦。

第一是版本兼容性。MSFlexGrid这类控件在老系统上跑得好好的,到了新服务器、64位环境、新版WinCC上经常出现注册失败、显示异常的问题,维护成本很高。

第二是C脚本操作它非常不顺手。想动态设置列头、合并单元格、按条件改变行背景色,要在C脚本里通过COM接口操作,代码写起来奇丑无比,调试困难。

这也是为什么我不再用ActiveX表格控件,而是转用HTML模板来渲染报表。

4.2 用HTML模板做表格渲染的完整思路

思路其实很简单:报表本质上就是一个二维表,而HTML的table标签天然支持二维表呈现,还支持CSS控制所有样式。

步骤是这样的:

  1. C脚本从数据库查出数据;
  2. C脚本打开一个HTML模板文件,把查询结果拼接成table行;
  3. 把完整的HTML写入本地文件;
  4. WinCC画面中的WebBrowser控件加载或刷新这个文件;
  5. 操作人员在画面上看到的就是一个完整报表。

这样做的好处非常明显。表格样式完全自定义,要加表头合并、列宽调整、隔行变色、数字高亮,只需要修改CSS;要支持打印,直接在浏览器里按Ctrl+P;要导出Excel,文件扩展名改一下就行。而且HTML文件不依赖任何WinCC版本,任何一台电脑都能打开。

4.3 动态生成HTML报表的C脚本示例

下面是一个简化的C脚本生成HTML报表的示例:

FILE* fp = fopen("D:\\report\\report_output.html", "w"); if (fp == NULL) { return; } fprintf(fp, "<html><head><meta charset='utf-8'>"); fprintf(fp, "<style>"); fprintf(fp, "table{border-collapse:collapse;width:100%%;font-family:SimSun;font-size:14px;}"); fprintf(fp, "th,td{border:1px solid #666;padding:6px;text-align:center;}"); fprintf(fp, "th{background-color:#2a5caa;color:#fff;}"); fprintf(fp, ".total-row td{background-color:#f0f0f0;font-weight:bold;}"); fprintf(fp, "</style></head><body>"); fprintf(fp, "<h2>交接班产量报表</h2>"); fprintf(fp, "<table>"); fprintf(fp, "<tr><th>设备编号</th><th>产量</th><th>合格率</th><th>停机时间</th></tr>"); // 循环读取查询结果集,这里用DLL封装函数示意 while (rs_next(rs_handle) == 0) { char deviceNo[32]; char outputQty[32], okRatio[32], stopTime[32]; rs_get_string(rs_handle, 0, deviceNo); rs_get_string(rs_handle, 1, outputQty); rs_get_string(rs_handle, 2, okRatio); rs_get_string(rs_handle, 3, stopTime); fprintf(fp, "<tr>"); fprintf(fp, "<td>%s</td>", deviceNo); fprintf(fp, "<td>%s</td>", outputQty); fprintf(fp, "<td>%s</td>", okRatio); fprintf(fp, "<td>%s</td>", stopTime); fprintf(fp, "</tr>"); } fprintf(fp, "</table></body></html>"); fclose(fp);

这里有一个细节要注意:html文件字符集,我统一用utf-8,同时SQL查询出来的中文字符串必须确保来源数据库也是正确的字符集,否则页面上会出现乱码。这个问题在后面排查部分会详细展开。

4.4 刷新与性能:大数据量分页

HTML报表文件生成之后,最关键的一步是让WebBrowser控件刷新加载。新版本WinCC的WebBrowser控件支持直接设置URL刷新,也可以在HTML文件末尾加一段meta标签定时刷新:

<meta http-equiv="refresh" content="30">

但真实项目里我很少让整个报表页面自动刷新,因为报表数据量大时重新查询并生成HTML需要时间,频繁刷新会让操作员烦躁。

更合理的是采用按钮触发刷新,并在C脚本侧做分页查询。数据库查询用OFFSET FETCH或者LIMIT,C脚本每次只取当前页数据,HTML页面底部生成“上一页”“下一页”链接。这样数据量再大,画面也不会卡。

分页还有一个隐藏的好处:生成HTML文件的时间短,WebBrowser控件加载快,不会出现报表页面空白半天的尴尬。

5. 实测必踩的坑:从“脚本未结束”到“握手错误”

5.1 C脚本“语句未结束”的九成原因

WinCC的C脚本语法检查和传统C语言类似,但它对错误提示并不友好。“脚本语句未结束”这个报错,我见过太多次,绝大多数原因就四种。

第一种是分号和引号不匹配。这个最基础,但也是最常见的。尤其在sprintf拼接SQL时,引号层级一多,很容易漏掉一个双引号。我的建议是:写完整条SQL字符串之后再整体数一遍引号。

第二种是中文标点。现场同事在中文输入法状态下写了一个中文分号、中文括号,编译时提示的报错位置经常和实际错误位置不一致,排查起来很迷惑。所以C脚本一律切英文输入法。

第三种是变量声明位置不对。WinCC的C脚本对变量声明的位置有限制,它要求函数开头把所有变量声明完,不能在循环内部临时声明变量。很多新手在这里踩坑。

第四种是长字符串跨行。WinCC C脚本里字符串直接换行写在两行,经常报错。要么拼成两段字符串用sprintf拼接,要么写文件时分开多次fprintf输出,总之别直接在字符串字面量里换行。

5.2 外部数据库“握手错误”排查链路

“握手错误”这个报错很玄,通常会伴随“连接失败”或“访问被拒绝”一起出现。我一般按下面这个顺序排查。

先检查数据库服务本身。SQL Server有没有启动,TCP/IP协议有没有启用,默认端口1433有没有被占用。这些是基础数据库管理问题,但往往最先被忽略。

再检查连接字符串。服务器名写的是IP还是主机名,指定了实例名没有,用户名密码是否正确,数据库是否存在。连接串里任何一项写错,效果都是握手失败或者超时。

然后检查驱动位数。WinCC 7.x系列运行系统是32位进程,必须使用32位的数据库驱动;WinCC 8.x如果是64位安装,则要配64位驱动。这里最容易出错的点是:系统里装了更高版本的驱动,但连接串仍然写着老驱动名称,或者开发机上能连、跑到服务器上就连不上。

最后检查防火墙和安全策略。Windows防火墙对出站连接影响不大,但入站连接如果只允许白名单IP,WinCC服务器的访问也会被拦截。厂区工控网里很多服务器在独立网段,跨网段访问数据库要确认网络策略。

我建议所有项目都做一个“数据库连接自检画面”,放一个按钮,点击后用简短的C脚本测试连接,返回错误码和错误描述。项目交付后,现场维护人员可以通过这个自检页面快速定位问题,不用天天打电话找我。

5.3 中文乱码、日期格式与驱动位数

中文乱码是报表项目中绕不开的坑,根源一般在三个地方。

第一,数据库表的字段类型。SQL Server里报表相关字段尽量用NVARCHAR/NTEXT,不要用VARCHAR/TEXT。VARCHAR存中文依赖数据库代码页,跨语言环境的服务器上很容易乱码。

第二,ODBC驱动的字符集转换。老版本SQL Server驱动和新的驱动在字符串处理上有差异,如果连接串里指定了字符集参数,务必确认和数据库实际编码一致。

第三,HTML文件本身的字符集声明。文件内meta标签声明的字符集必须和实际写入的字符集一致,我用的是UTF-8。

日期格式也是个暗坑。不同数据库的日期格式不同,C脚本拼接SQL时习惯用YYYY-MM-DD HH:MM:SS字符串,但要注意区域设置。最稳妥的写法是在连接串里指定区域语言标识,或者在SQL语句中使用标准格式转换函数。

还有驱动位数,确认方法是打开WinCC运行系统进程,查看进程位数,然后安装对应的数据库驱动。在ODBC数据源管理器里能看到同样一份驱动列表,连接测试能通过、脚本里报握手失败,多数都是进程位数和驱动位数不匹配。

5.4 关于WinCC运行环境与授权的提醒

最后说一个容易被忽略的问题:WinCC从旧版本迁移到新版本,比如现在很多项目升级到WinCC 8.1,除了功能变化,还有授权方式的变化。正版授权的部署要严格按照官方要求,授权文件的备份、恢复顺序、服务启动顺序都要检查。

我遇到过这样的情况:项目部署到新服务器,所有功能都正常,但报表脚本一直报连接异常。排查半天才发现是WinCC运行服务启动顺序问题,数据库连接需要的系统服务没有正常拉起。重启服务后问题消失。

这里也顺便提醒一点:所有涉及WinCC运行环境的改动,都要先在测试环境验证。生产项目上直接改脚本、改授权、改数据库连接,风险很高。

最后再说点实际的

这套模板走到今天,也是被一个又一个项目逼出来的。最早我每接一个项目都要从零写报表脚本,后来发现套路其实是固定的:连接数据库、查询、生成表格、写入数据,翻来覆去都是这些动作。于是我把这些固定动作全部抽成通用函数,把变化的部分全部放到数据库配置表里,这才有了这套通用的数据库报表模板。

如果你正在被WinCC报表功能折磨,我的建议是先不要急着买昂贵的报表插件,先把你项目里真正的报表需求梳理清楚:哪些是过程值趋势,哪些是业务统计。前者用WinCC自带能力就够,后者用我这个外部数据库加脚本的方案,基本能覆盖大部分场景。

后续如果甲方有更高级的BI分析需求,还可以在这个模板的基础上对接帆软这类专业报表工具,数据库层完全一致,只是把HTML展示换成专业报表平台的展示页。数据已经进了外部数据库,往上怎么延伸都有空间。

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

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

立即咨询