****项目背景:****使用LabVIEW 2015开发的上位机,同时支持Kvaser和周立功(ZLG)USBCAN系列CAN卡。客户现场仅使用Kvaser,但程序在启动时会因找不到zlgcan_wrap.dll而报错"未找到资源"。
****解决目标:****使程序在不安装周立功驱动的电脑上也能正常启动,且不影响原有的Kvaser功能。
****核心思想:****将静态加载改为动态加载。即,将周立功相关的VIs封装起来,通过"打开VI引用"在运行时才加载,而非程序启动时。
一、整体解决方案架构
- ****封装层:****创建一个统一的VI(ZLG_CAN_Operations.vi),作为所有周立功操作的入口。
- ****动态调用层:****在主程序中,使用 打开VI引用 → 通过引用调用 → 关闭VI引用 的框架来调用封装VI。
- ****状态管理层:****在封装VI内部,使用移位寄存器维护设备句柄等关键状态,对外部调用者透明。
二、核心实施步骤
步骤1:封装周立功VIs(ZLG_CAN_Operations.vi)
****目的:****将零散的ZCAN_OpenDevice.vi、ZCAN_InitCAN.vi等VIs集中管理,对外提供统一接口。
关键设计:
- 输入控件:
- Command (枚举):定义 Open, Init, Start, Transmit, Receive, Reset, Close, Status 等命令。
- Device Type (U32):选择硬件型号(USBCAN-I / II)。
- Device Index (U32):设备索引,通常为0。
- CAN Channel (U32):通道号(0或1)。
- Baud Rate (U32):波特率。
- TX Data (自定义簇):包含ID、数据长度、数据数组等发送信息。
- error in (错误簇):标准错误输入。
- 输出控件:
- Device Handle Out (U32) / Channel Handle Out (U32):内部使用,不对外暴露。
- RX Data (数组或簇):接收到的CAN帧数据。
- 通道已打开? (布尔):用于 Status 命令。
- error out (错误簇):标准错误输出。
- 内部逻辑:
- 使用 Case Structure 根据 Command 值分发到不同分支。
- 使用 移位寄存器 (Shift Register) 在VI内部维护 设备句柄 和 通道句柄,状态不对外暴露。
- Status 命令:读取内部通道句柄,判断是否大于0,并输出布尔值。






连线板 (Connector Pane):
分配接线端时,仅暴露必要的输入(Command, Device Type, Device Index, CAN Channel, Baud Rate, TX Data, error in)和输出(RX Data, 通道已打开?, error out)。不暴露任何句柄。
VI属性设置:

- 重入执行:选择 "预分配的副本重入执行",确保多线程安全。
- 允许调试:取消勾选。
- 启用自动错误处理:取消勾选。
步骤2:在主程序中实现动态调用
****目的:****将所有原本直接放置的ZCAN_*.vi,替换为动态调用ZLG_CAN_Operations.vi的框架。
调用框架(标准模式):
- 打开VI引用 (Open VI Reference):
- 路径:设置为相对路径(如 ZLG_CAN_Operations.vi),或使用 当前VI路径 → 拆分路径 → 构建路径 动态获取。(说明:图示ZLG_CAN_Operations.vi与主vi在同一目录下。)

- 类型说明符:右键创建常量,然后将ZLG_CAN_Operations.vi拖拽到该常量上以完成配置。
针对 LabVIEW 2015 的 拖拽法的 正确操作步骤 :
- 确保"类型说明符"输入端已创建常量:程序框图上应该有一个显示为 "<无>" 或 "VI 引用句柄 (仅用于类型)" 的常量,它连接在 打开VI引用 节点的 类型说明符 输入端上。

- 打开 ZLG_CAN_Operations.vi:在项目浏览器中,双击打开这个 VI,确保它的前面板或程序框图是打开状态。
- 在项目浏览器中,找到并选中 ZLG_CAN_Operations.vi(但不要双击打开它)。
- 用鼠标按住这个 VI 的图标。

- 将图标拖拽到程序框图上,直接放到那个显示为 "<无>" 的常量上面,然后松开鼠标。

- LabVIEW 会自动识别这个 VI 的类型信息,并更新该常量。

- 通过引用调用 (Call By Reference):
- 将 打开VI引用 输出的 VI引用 连接至此节点的 引用 输入端。
- 节点会自动匹配封装VI的输入输出接线端。
- 根据业务需要,将 Command 设为对应操作(Open, Init 等),并连接其他输入数据。
- 关闭VI引用 (Close VI Reference):
- 将 通过引用调用 输出的 引用 连接至此节点,释放资源。




模式选择:
- 本项目中采用 "每次调用都打开和关闭引用" 的模式。这对初始化、配置等低频操作足够,且逻辑清晰。
- 对于高频循环(如持续接收),可考虑将 打开VI引用 置于循环外,循环内仅调用 通过引用调用,循环结束后再关闭,以提升性能。
步骤3:处理Kvaser与周立功的分支
- 在原有的Case Structure(根据CAN卡型号选择)中:
- Kvaser分支:保持不变,继续静态调用Kvaser的VIs。
- USBCAN-I / USBCAN-II 分支:全部替换为动态调用ZLG_CAN_Operations.vi的框架。
- 特别注意:Transmit、Receive、Reset、Close等所有操作,都必须按此模式替换,确保无一遗漏。
步骤4:生成EXE与部署
- 在"程序生成规范"中:
- "源文件"设置: 必须将 ZLG_CAN_Operations.vi 和 ZLG_Driver 文件夹(包含其所有子VI)添加到 "始终包含" 列表中。

- 如果有类似 zlgcan_vi_lib.vi 这种生成时报错的库文件,而您的程序并未实际使用其功能,可以从"始终包含"列表中移除,以解决生成错误。
- 验证:
- 在未安装周立功驱动的电脑上运行EXE。
- 预期结果:程序正常启动,无任何"未找到资源"或"外部函数不存在"的错误。
三、关键问题排查记录
|----------------------------------------------|-------------------------------------|-----------------------------------------------|
| 问题现象 | 可能原因 | 解决方案 |
| 启动时弹出"未找到资源 zlgcan_wrap.dll" | 仍有ZCAN_*.vi被静态调用(直接放置在框图上) | 检查所有VI,将静态调用全部替换为动态调用框架。 |
| 生成EXE时报"VI加载后断开" | 某些库VI(如zlgcan_vi_lib.vi)引用了缺失的子VI | 从"始终包含"列表中排除该库VI(前提是程序未实际使用其功能)。 |
| 在干净电脑上启动EXE后,使用周立功模式时崩溃 | 动态调用的路径设置不正确(如使用了绝对路径) | 使用相对路径或 当前VI路径 构建路径。 |
| 修改了ZLG_CAN_Operations.vi接口后,主程序中的通过引用调用节点未更新 | 类型说明符未刷新 | 右键点击 通过引用调用 节点,选择"重新链接至类型说明符";或断开并重新连接其引用输入线。 |
四、后续优化方向
- 创建快捷方式VI:为 Open, Init, Start 等常用命令分别创建独立的封装VI(如 ZLG_Open.vi),内部调用 ZLG_CAN_Operations.vi 并预设好 Command。这样主程序调用时接线端更少,更简洁。
- 提升性能:对于高频操作(如接收循环),将 打开VI引用 节点移至循环外部,仅调用一次,循环结束后再关闭。
五、总结
本项目通过 "封装 + 动态加载" 的模式,成功解耦了程序逻辑与特定硬件驱动,使得一个应用程序可以平滑地适配不同硬件环境,且不会因缺失某个驱动而无法启动。此方案可推广至其他需要加载第三方DLL或驱动的LabVIEW项目中。
核心收获:
- 区分静态与动态加载:静态加载发生在VI打开时,动态加载发生在运行时。
- 封装内部状态:使用移位寄存器管理句柄,对外隐藏实现细节。
- 路径管理:使用相对路径或动态构建路径,确保EXE可移植性。