LabVIEW 的一些问题
这一节,记录笔者在使用 LabVIEW 过程中遇到的一些问题,以及 LabVIEW 一些不太完善的方面。
LabVIEW 的 unicode 问题
笔者最近试图去查看自己很久之前写的一些 VI,但是却发现那些 VI 要么无法打开,要么打开后,里面的中文字符全部成了乱码。造成个这问题的根本原因是因为 LabVIEW 不支持 unicode 编码。
先简略的介绍一下 unicode 的背景:
计算机是在美国被发明的,所以当时很自然的就只考虑了处理英文。最早出现的一个,也是最著名的一个关于在计算机中表达字符的标准,是 ASCII 标准(American Standard Code for Information Interchange,美国信息互换标准代码)。它定义了128个字符,包括英文字母大小写,数字,常用的标点和一些特殊符号。当时世界上所有的计算机都在使用 ASCII 方案来保存英文文字。这个方案最大的问题是它只包含了英文字母,于是其它国家,组织和公司纷纷开始扩展这个标准,用以支持其它字符,比如制表符、数学运算符号、中文字符、日文字符等等。在中国,最常用的标准,包括 GB2312,GBK,GB18030 等都是对 ASCII 的中文扩展。这些扩展出的标准有一个很麻烦的问题:同一个数值在不同的标准中被定义了不同的含义。比如,某一数值在中文的标准下可能是一个中文字符,在韩文的标准下,可能就是一个完全不相关的制表符。这就造成了,在中文环境下开发的软件,运行到韩文系统下,显示的就完全是乱码。如果有人需要在一个系统下同时运行一个中文软件和一个韩文软件,就只能 有一个软件可以正确显示文字。
为了解决这个问题,1990年开始,计算机业界开始研发一种新的编码标准,它可以覆盖全世界所有的字符,也就是说,任何一个字符都有自己独占的编码,在任何系统下都会保持这个编码,这样就不会出现换个系统就乱码的问题了。这就是 unicode 编码,也叫万国码、单一码。unicode 规定了字符集,但是这套字符集也还存在多种不同的编码格式。Windows 采用了 UTF-16LE 格式的 unicode 编码(UTF全称为 Unicode Transformation Format),使用 16 位的(双字节)数据表示一个字符。但是当前最流行的 unicode 编码格式却是 UTF-8,这是一种变长的编码方式,根据字符的常用程度,使用不同长度的编码来表示这个字符,编码长度有可能是 1 到 6 个字节。目前大多数的 unicode 文档采用的都是 UTF-8 编码。
经过几十年的推广和发展,现在已经很少有不支持 unicode 的主流软件了。但是,非常遗憾的是 LabVIEW 始终没有支持 unicode。在整个2010年代,NI 公司的重点放在了开发支持 unicode 的 LabVIEW NXG。计划用它彻底取代 LabVIEW,所以没有投入足够的资源去改进 LabVIEW。但后来,NI 公司又放弃了 LabVIEW NXG。目前,不支持 unicode 的 LabVIEW 仍然是使用者的唯一选择。
虽然 Windows 很早就开始支持 unicode 了,但是为了兼容那些还没有支持 unicode 的软件,Windows 系统保留了一个默认字符集的设置。对于非 unicode 的软件,Windows 会使用默认的字符集来解释那些字符编码。国内使用的 Windows 操作系统,无论是中文版还是英文版,几乎都把默认的字符集设置为了中文字符集。所以,一个软件是否支持 unicode,对于绝大多数用户来说,都是没有差别的。笔者之前也从来没意识到 LabVIEW 不支持 unicode 会有什么问题。
直到最近,笔者才开始注意到这个问题的严重性。笔者家里有两台电脑,一台操作系统安装了 Windows;另一台操作系统是中文 Deepin Linux。两台电脑上都安装了社区版的 LabVIEW 2021。笔者发现,在 Windows 系统下编写的 VI,如果包含中文常量或注释,拿到 Linux 系统下打开,看到的就全部是乱码;反之亦然。更严重的是,如果项目、库、类等,如果包含了有中文名的 VI,那么,把项目拿到另一个系统下就根本无法打开了。
造成这些问题的根源在就在于 LabVIEW 没有直接支持 unicode,它总是采用操作系统的默认字符编码对字符进行编解码:在中文 Windows 系统下使用 GB18030 中文编码;在 Linux 系统下使用 UTF-8 编码。
因为 Linux 系统普遍采用了 unicode,也没有别的默认编码,所以 VI 在不同版本的 Linux 系统上的行为和显示内容都是一致的。但 Windows 系统本身采用的是 unicode 编码,为应用程序提供的却又是可以由用户设置的默认编码。假设,在一个项目中有两个 VI,分别是“界面.vi”和“任务1.vi”,其中“任务1.vi”是“界面.vi”的子 VI。在操作系统层面,有两个文件:“界面.vi”和“任务1.vi”,它们的名字都是使用 unicode 编码保存的。但是在“界面.vi”内部,它记录了自己要调用“任务1.vi”,用的却是非 unicode 编码(比如在中文 Windows 系统下使用 GB18030 中文编码)保存的子 VI 的名字。所以 LabVIEW 中用于记录这个子 VI 的名字的一段二进制数据与操作系统中记录这个子 VI 文件名使用的二进制数据是不同的。每次 LabVIEW 需要操作系统装载一个子 VI 文件时,还需要做一次编码转换,把文字转为系统使用的 unicode 编码。如果保存 VI,和读取 VI 都是在中文 Windows 中,编码始终一致,不会有任何问题,VI 名字总能被正确转换。
当我们把之前在中文 Windows 系统下保存的项目拿到非中文 Windows(默认语言编码不再是 GB18030)下打开,或是拿到 Linux 系统上打开,会发现,我们会发现 VI 文件的名字都还是对的,因为操作系统采用的编码都是 unicode。但是,在 LabVIEW 中打开 VI 就会出错了。在 LabVIEW 内部,解析子 VI 的时候,它会试图用新的系统默认编码来解释原本使用 GB18030 格式保存的子 VI 名,结果自然得不到正确的 VI 文件名,也就无法找到正确的子 VI。同理,中文 Windows 下保存的 VI,如果有中文名,拿到 Linux 系统下打开也会出错;反过来也是一样会出错。
总之,由于 LabVIEW 在 Windows 下没有使用 unicode,造成了中文(或其它非英语文字)在不同系统下显示行为的不一致,切换系统就会出现乱码,甚至 VI 无法被加载。目前,笔者也没有找到好的办法解决这个问题。笔者只能尽量在 LabVIEW 中只使用英文,不使用中文以及特殊字符。
如果有一天 LabVIEW 开始支持 unicode 了,那么这很可能意味着用户使用之前版本的 LabVIEW 编写的项目,如果有其它语言文字的话,会无法在 unicode 版本 LabVIEW 下打开,或是打开后出现乱码。这将会是一个非常严重的兼容问题。这可能也是 NI 公司迟迟无法下决心支持 unicode 的原因之一。
LabVIEW 的源代码管理
LabVIEW 源代码管理一直是个非常麻烦的问题。
文本编程语言有个大优势,文本有统一标准的文件存储格式。不论那种文本编程语言,都可以使用通用的工具进行编辑,排版,存取,跟踪修改历史等等。市面上有着大量成熟的工具可以完成这些工作。而 LabVIEW 是一种非文本的编程语言,它也没有使用任何一种通用的开放的文件格式来保存代码,所以,除了 LabVIEW 自己,没有任何其它工具能解析编辑 LabVIEW 代码。所以,也只有 NI 公司自己才与可能去开发适合 LabVIEW 的源代码管理工具了。只是不知道 NI 公司有多少资源可以分配给这个问题。可以预期,就算将来 LabVIEW 在这方面有所改进,也不太可能像管理文本代码一样方便的管理 LabVIEW 代码。
除了源代码管理,LabVIEW 在交流和学习方面也非常不方便。比如在论坛里张贴一段示例代码,文本编程语言直接复制粘贴文本就可以,LabVIEW 代码只能截图,如果遇到层次较多的条件结构,或者事件结构,一张截图恐怕还不够。直接张贴 VI 文件,读者也是无法看到 VI 内容的,需要在 LabVIEW 中打开 VI 文件。如果读者的 LabVIEW 版本较低,可能根本就无法打开 VI 文件进行查看。
我觉得最理想的情况是,LabVIEW 可以把程序的逻辑、前面板的界面、节点和连线的排布、编译好的代码等几个部分完全隔离开,分别保存。程序员只需要关注程序的逻辑部分。前面板,程序框图的布局可以根据不同用户各自的规范完全自动排布。而程序的逻辑部分也可以使用文本格式进行保存,这样就可以完美的集成任何一个文本源代码管理工具了。
LabVIEW 程序框图的缩放
我最早使用 LabVIEW 的时候,电脑显示器的主流分辨率还是 640*480
,那时候一满屏也显示不下多少代码。如果某个 VI 程序框图比较大,就必须挪来挪去一块一块查看。这也是 LabVIEW 代码难以阅读的一个重要原因。虽说有缩略图,但是缩略图里只能看到个大概轮廓,根本看不出程序逻辑,帮助十分有限。那时候就想,如果可以把程序代码的视图稍稍缩小一点,让我能一次多看到一些代码就好了。
我现在使用的显示器分辨率都在 3840*2160
以上,LabVIEW 一个子 VI 的图标一般也就 32*32
个像素,连屏幕宽度的百分之一都不到,有些函数和节点的图标就更小了。再加上现在眼神也不好使,很多代码的逻辑都看不清楚了,比如是加法,减法还是乘除法?LabVIEW 同样不支持把代码视图放大。好在现在的操作系统都视图缩放了,使用系统提供的功能把 LabVIEW 画面放大个两三倍是我感觉最舒适的大小。可是这时候就会发现,其它程序放大两三倍,图像依然清晰光滑,而 LabVIEW 却显得粗糙模糊了。究其原因,LabVIEW 的显示用的全是像素图,而非矢量图。LabVIEW 中,如果换一个图标需要用 1K 个点表示,边长放大 3 倍,它还是只有 1K 个点;而其它软件通常就会用 9K 个点来表示放大后的图标。同样面积下,显示点数只有别人的 1/9,当然模糊了。
显示逻辑也是 LabVIEW 最底层的最基础的部分之一,改动它也是个棘手的问题,不知 LabVIEW 何时才能有所优化了。
逐渐失去的优势
LabVIEW 在某些方面依然优势明显:比如画图就是比写文字看着更有趣;它还可以简便地连接和读取 NI 的硬件采集的数据。但不得不说,在过去的十来年里(2012~2022),LabVIEW 并没有太多改善,而同时期,其它主流的编程语言,却都有明显进步,以至于我觉得当年 LabVIEW 一些独特的优势,现在都不再那么突出了。我感受比较深的是两点:
调试便利性
LabVIEW 的一大优势是可以单独运行一个 VI,这会让程序调试非常便利。以前调试 C 语言程序的时候,即便只改一行代码,也要重新编译整个程序才能观察修改后的结果。如果是个大项目,重新编译、重现出问题时候程序的状态都是非常麻烦的。但如果可以只运行一个函数,或者 VI,那就可以省去之前提到的麻烦,快速验证修改的结果了。
然而,在过去的十来年里,很多主流的编程语言都开始改进了类似的用法。最典型的是 Python。Python 是一种解释型语言,本来就不必编译整个程序,可以单独运行一行程序。只不过,在传统的编辑环境中单独运行某几行代码还是不太方便。但是后来出现的,以 Jupyter Notebook 为代表的基于网页的编程环境大大改善了这一用法。在 Jupyter Notebook 中,程序代码被分成了小块,点个按钮就可以运行某个小块内的代码。每个小块内可以包含任意多的函数、变量,这甚至比 LabVIEW 运行单个 VI 还方便。
此外,Jupyter 编辑环境完全基于网页,没有一个独立的程序界面。运行 Jupyter,它会开启一个网络服务,然后就可以使用网络浏览器来查看、编辑、调试程序了。这极大便利了远端编写程序。以前要想验证一段程序对不对,必须现在自己电脑上安装那个编程语言的编译器,然后才能编译测试。如今,网上可以找到很多在线的编程环境,支持所有主流的编程语言。只要打开那些网站的网页,选择相应的语言,然后直接在网页中输入程序代码,就可以运行调试。
LabVIEW 也曾经搞过类似的网页版本的 LabVIEW UI Builder,可是它跟 LabVIEW 程序并不兼容,属于是另一种语言了。要想运行调试 LabVIEW 还是不得不在自己的电脑上安装一个巨大的安装包。
并发
可以方便地编写多线程程序也是 LabVIEW 的优点,而且 LabVIEW 有自己的执行系统,即便是在单线程下,也可以并行运行程序框图上两个没有依赖关系的模块。这对于读写仪器数据这类大量 I/O 操作的程序是个极大的优势,可以在不需要程序员进行额外编程的情况下,就大大提升程序效率。主流的文本编程语言也都支持编写多线程程序,但是,在十几年前,它们对于多线程的支持都是比较繁琐的,要编写一个多线程程序就不得不考虑数据同步、安全等棘手的问题。不过最近这些年,各种高层的工具包被开发出来,编写多线程程序变得容易多了。尤其是,编程语言对于异步 I/O 的支持,让并发操作变得更容易了。
比如,Python,JavaScript,PHP 等都支持异步 I/O 了。异步 I/O 依赖编程语言自己管理和调度,有点类似于 LabVIEW 的执行系统,可以在单线程下并发运行不同的代码块。异步 I/O 可以使用少量代码,就解决那些慢速操作(比如外设读写、网络连接等)阻塞整个程序的问题,同时也不需要开发者去关注数据同步之类的安全问题。
以 Python 为例:
async def foo(some_arguments):
# read a file
return "something from foo"
async def bar(some_arguments):
# write to a database
return "something from bar"
async def main():
results = await asyncio.gather(
foo("file_name"),
bar("database"),
)
这段程序只用了一条语句就可以让函数 foo 和 bar 并发运行,一点也不比 LabVIEW 中实现并行运行更困难。