水痘

老婆怀孕之后,我们家就开始到处漏水。

最开始是卫生间洗手池的上水管漏水。
接下来是楼上厨房间漏水,流到我们家来,把我家的壁橱全部泡烂了。到现在,所有壁橱都变了形,还散发着一股霉味。
上个月,小区上水系统改造,水压增加不少。这本来是件好事,但是没两天楼下就来找我们,说他家的厨房天花板潮了。经过一番检查,我在家里没找到任何可见的漏水点,据推测只能是厨房地砖下铺设的暗管漏了。我当年买的是二手房,带装修的。打电话给原来的上家,他也不记得水管是如何铺设的了。在不清楚水管铺设的情况下,扒开地砖修补必然是个大工程。我自己是没法弄了,于是在网上找了一个装修工,又请来老爸帮忙,才终于赶在中秋节前,把漏水的暗管修好。但厨房地面却留下了一块永久性的伤疤。
节前在京东订购了两根洗衣机的上水管,没想到拿来一试,居然全是漏的,只好退货。看来,网上购物,不论大小都一定要选品牌有保障的商品才行。像上水管这种小东西,我以为不同品牌在质量上不会有多大区别,结果被京东忽悠了,用不低的价格卖给我一个杂牌货。
趁着过节有时间,跑了一趟水龙头的维修点,为我长期漏水的几个水龙头采购了崭新的配件换上。除去由于缺少小扳手,不能更换的卫生间淋浴器上的一个水阀,家里其它龙头都修补好了。
正打算庆祝一下,终于基本上解决了家里的漏水问题的时候,突然发现卫生间洗手池的下水管又开始漏了。哎,不知道啥时候才能把问题都解决完了。

跟老婆讨论漏水问题的时候,我们一致认为这都是小宝带来的,TA命理喜水。老婆给宝宝想了一个小名,叫豆豆。我说,既然TA这么喜欢水,咱们就叫TA水豆吧。

 

高俅若是弱智还好

最近一个颇受争议的新闻是说某著名的国籍不详的“球星”即将被任命为高官。这让我立刻联想到了中国历史上一位大家都耳熟能详的高官球星-高俅。(人家名字起的真好)这阵子在追看一个水浒的评论贴“解密水浒中的生存智慧”,所以很容易想到宋朝哪去。
话说高俅被提拔成太尉之后,人们议论纷纷:打球打得好是不是就可以做官?看新浪上的回帖,都是称赞高俅的,说他不但球打得好,其它能力也出众,必定胜任领导岗位;看凯迪社区的回帖,大部分都是贬高俅的,说他只会打球,但人品可疑,是个裸官。
在我看来,球技应当是与领导能力无关的。一个人打球打成世界第一,既不能说明他适合当官,也不能说明他不适合当官。更何况一个人当官当成什么样主要还不是看个人能力和人品,而是靠制度:若他的官位是老百姓投票选出来的,则官员必然克制私欲,想法设法讨好百姓;反之,若官位是皇上给的,则官员必然欺下媚上,贪得无厌。
高俅的官位就是皇上给的。说到底,皇上(贵族)的利益与百姓的利益是矛盾大于统一的,因此皇上给高俅这个官当,就是让他去削弱民力,祸害百姓的。从这个角度考虑,我宁可高俅是一个四肢发达,头脑简单的蠢材,这样屁民的日子才不会更难过。

回调VI

LabVIEW界面程序最常用的结构就是循环事件结构。用事件结构截获用户在界面上对控件的操作,然后做出相应处理。

在文本语言中,常用的事件处理方法与LabVIEW是不同的。文本语言常常使用回调函数来处理界面事件。比如:某个按钮按下时,需要做一个fft运算。那么就写一段函数来完成这个fft运算,再把这个函数与按钮按下事件关联起来。开发语言通常已经做好了对事件的监控,一旦发现按钮按下事件产生了,就去调用与它关联的fft运算函数。这个有开发者编写,被系统调用的函数就叫做回调函数。

LabVIEW也可以采用与文本语言相类似的方法来处理事件:不是在事件结构内处理,而是在程序开始时,就为某事件注册一个回调VI。在回调VI内编写相应代码,一旦事件发生,这段代码就会被执行。

与事件结构相比,回调VI编写起来稍微麻烦一点;但它的好处是,它和主VI是平行运行的。如果事件处理过程比较耗时,把它放在事件结构中会阻塞整个程序,使得程序界面暂时失去响应;而把它放在回调VI中,则不会影响程序其它部分的运行。

比如下面这个例子。程序界面上有两个仪表盘:左面那个始终在运转,每10秒钟旋转一圈;右边那个,由按钮控制,按下按钮才旋转一圈。若把旋转右表这个工作放到事件结构的按钮按下处理分支中去做,它势必会打断左表的旋转,因此,考虑把它放到回调VI中去做。

这是主程序界面:两个表盘,和一个控制右表旋转的按钮。

程序的代码也比较简单。先看代码的右半部份:这是一个典型的循环事件结构,用来控制左表的旋转。但是注意,右表的控制并不是在这个结构中实现的。

再看程序左半部分:它为按钮“右表旋转一圈”的值改变事件注册了一个回调VI。

注册回调VI用的是节点“Register Event Callback”,它在函数选板“Connectivity –> ActiveX”上。这个节点主要是为了给ActiveX、.NET控件的事件注册回调VI。事件结构无法截获ActiveX、.NET控件的事件,因此只能通过回调VI的方式来处理这些控件的事件。但是这个节点也可以用于给LabVIEW自带的控件注册回调VI。

注册回调VI节点,有三个输入参数从上至下分别是:事件的发出者、回调VI、用户自定义数据。

在我们这个例子中,需要截获的是按钮“右表旋转一圈”的值改变事件,因此需要把“右表旋转一圈”控件的引用作为第一个参数传递给注册回调VI节点。指定好事件的发出者,接下来需要选择事件的类型,鼠标点击注册回调VI节点的第一个参数的接线方块,发现“右表旋转一圈”按钮的所有事件都已经列在这里了,选择“值改变”事件。

第三个参数是用户自定义数据,可以是任意类型的数据,在回调VI中需要用到的数据都可以通过它来传递。因为我打算在回调VI中对控件“右表”做修改,因此,在这里把“右表”的引用作为数据传递给回调VI。

第二个参数是回调VI的引用,如果已经写好了回调VI,把引用传进去就行了。我还没有编写回调VI,因此可以在参数的接线端上点击鼠标右键,选择“Create Callback VI”创建一个空白的回调VI。

回调VI中写一小段代码,让右表旋转一圈,整个程序就完成了。这时,左右表可以各自运行,互不影响。

LvClass 的一个效率问题

前几天,听到了一个客户的抱怨:他编写了一个LabVIEW程序,每次打开主程序就要花费几分钟的时间,这有点令他忍无可忍。我没有见过他的源程序,不过据帮他检查过程序的同事讲,他的问题很可能是使用了大量的LvClass造成的。在他的项目中,包含有上百个类(LvClass)。我以前也听说过LvClass在效率上可能会有些问题,听到了这个消息后,我自己做了一个实验。

LabVIEW Scripting中有一个属性节点可以用来查看内存中所有的VI,我就利用这个VI来查看一个程序到底在装入些什么,令它启动如此之慢。

假设不存在子VI,如果打开某个不在LvClass中的VI(即便这个VI是属于某个lvlib的),只有这个VI会被装入内存。但是,打开某一个LvClass中的VI,我发现不但这个VI会被装入内存,它所在的类中的所有其它的VI也都被调入内存。如果这个类还有父类和祖先类,那么所有父类、祖先类中的VI统统都会被调入内存。

总结一下就是这样:当一个VI被装入内存

  1. 它的所有子VI都会被装入内存;
  2. 它所在的类中的所有的VI都会被装入内存;
  3. 它所在的类的父类中的所有的VI都会被装入内存。

以上3条可以是递归发生的,比如一个主VI A被装入内存,它的子VI B也会被装入内存,和B同属一个类的VI C也要被装入内存,C中有个子VI D,D属于类E,E有个父类F,F中有个方法VI G。尽管G的功能和程序A八杆子都打不着了,但也会被装进来。这大概就是那个用户遇到的问题,表面上他的程序不算太大,但是程序开始启动时,却需要把多于程序本身数倍的不相关的VI都装入内存,这一过程会每次都浪费他几分钟的时间。

鉴于LvClass的这一特性,设计使用它的时候一定要格外小心,否则很可能会造成程序效率的低下。我想到了几点需要注意的地方:

  1. 如果仅需要对一些VI进行封装,那么应当使用lvlib,而不是lvclass。两者封装的主要区别是,lvclass可以封装对象的属性(也就是模块用到的数据)。
  2. 类中的VI必须是高内聚的,类中的方法共同完成某一基本功能,不可再分割。应用程序一旦用到这个类中的某个VI,就意味着程序将会使用到类中几乎全部的VI;而不是一个应用程序可能只使用这个类中的某几个VI。
  3. 继承关系应当尽量简单。没有必要的时候尽量不使用继承。LabVIEW不支持接口,不应创建一个纯虚类,然后当作接口来用。
  4. 尽量不要嵌套调用。比如在一个类的VI中又去调用另一个类中的VI。
  5. 打算使用多态这个特性时要注意,多态使得应用程序在运行时,根据对象的类型选择对应的处理方法。但有些选择应当是程序编译时就做出的,它们不适合套用在多态特性上。

举一些例子:

  • INI文件读写这个模块比较适合做成类,每个INI文件对应一个类的实例。它有丰富的数据(文件的内容);它的方法有限,基本上只需要打开、读条目、写条目、保存关闭,这四个方法,并且一般的应用程序都会同时使用到这四个方法。
  • 复杂仪器的驱动程序不适合做成类。因为驱动程序会提供非常多的功能,示波器有各种触发模式。而一个应用程序通常只用到多种模式中的某一种就够用了。
  • 某测试程序可以生成测试报告给用户。用户可以选择几种不同的报告类型。生成报告的模块可以用lvclass来设计。因为生成不同类型的报告的方法间,可重用代码很多,可以为它们设计一个基类。并且,是程序运行时,才选择生成报告类型的。
  • 某一测试程序,可以支持多种型号的仪器。因为不同用户使用不同的硬件。对不同型号仪器的支持不适合使用lvclass来设计,因为测试程序发布给用户时,用户的硬件设备是固定的。对仪器的选择应当是程序发布时就决定好的,而不应等到程序每次运行起来后判断。

WPF界面编程与LabVIEW界面编程的比较

最近一段时间在学习使用WPF编写程序。计算机软件技术发展太快了,新的语言、体系,层出不穷。往往一个技术还没用熟练,就被要求换用更新的技术了。没准哪一天LabVIEW被什么其它语言替代了,我这方面的经验也就全作废了-程序员果然是吃青春饭的。

WPF是.NET中编写程序界面的一套体系。微软提出WPF时的构想是非常好的:它希望把程序的界面和运行逻辑部分完全分开来,这样可以有专门美工来设计程序界面;而程序员则专心实现程序功能的编码。它能不能实现这个美好的愿望呢?很难说,至少对于我和周边的同事来说,仍然需要同时负责相关程序的界面和代码。

虽然WPF之前,Visual Studio就号称支持可视化界面编程了,但那时可视化做的并不彻底。早期的VB、VC可视化编程,提供了一个可视化界面编辑环境。程序员在通过界面编辑器设计调整程序界面的同时,界面编辑器自动生成能够产生当前界面效果的VB或VC代码。也就是说,界面最终也还是程序代码的一部分,只不过可视化界面编辑环境帮可以助程序员生成相关的代码,从而简化了程序设计。
在WPF中,界面设计有了它自己的专门的记录方式,XAML语言,一种类似XML格式的文本语言。只有在程序进行编译的时候,编译器才把XAML代码编译成.NET的中间代码。这种行为与LabVIEW是类似的:界面与程序代码采用两套不同的机制表示。

与LabVIEW相比,WPF功能强大、灵活,但学习和使用难度更大。

先说LabVIEW的优点:LabVIEW的界面设计简单直观。WPF也许还不成熟,在可视化编辑方面做得并不好。最严重的问题是,一旦界面较为复杂,Visual Studio的界面编辑器就无法对其进行解析显示了。这样就完全失去了可视化编辑的功能。我现在接触的这个项目,有上百个XAML文件,超过7成的文件都无法被界面编辑器显示出来,只能手工的去修改XAML的文本。改完后,运行程序才能看到修改的效果。
再有,界面很少是静态的,界面上常有一些元素会根据程序运行时某些数据的不同而变化,因此,WPF上总有一些控件和程序中某些数据绑定起来。LabVIEW即便在编辑状态下,控件也可以拥有数据。这样以来,程序员在编辑状态下就可以看到程序运行起来后的控件状态。而WPF没有这一功能,程序只有在运行时才有数据,一旦进入编辑状态,所有数据都是空的。这样一来,在界面编辑器上看到的界面很可能与程序运行时的完全不同。

界面上的基本元素是控件,提供什么样的控件给程序员呢,LabVIEW与WPF采用了完全不同的策略。WPF面向通用编程,是给专业程序员用的,所以它侧重功能的强大与编程的灵活性。WPF自带的界面控件种类并不多,但是程序员可以很方便的就把它们组合起来,实现各种界面风格,完成各类复杂功能。LabVIEW只应用于某些领域,而且使用LabVIEW的不仅是程序员。因此LabVIEW侧重控件的易用性:它提供了大量自带的控件,每个控件可供修改的属性有限,如果想使用这些控件很容易:拖到VI前面板上就好了;如果想使用某种LabVIEW尚未提供的控件……那还是不要想了。
实际上LabVIEW今日的应用已远超出当时所定范围,相当一部分LabVIEW使用者都是专业程序员,他们对于功能和灵活性的要求越来越高,因此LabVIEW近几个版本也在这方面做了较大改进,比如增加项目管理功能,支持面向对象等。XControl便是转为补偿LabVIEW自带控件功能和灵活性不足而设计的新功能,但XControl使用起来太过复杂了,不如WPF的机制更加合理。也许将来的LabVIEW应该向WPF学习。
WPF中的控件差不多都可以作为容器再容纳其它控件。比如说按钮这个控件,它只提供一个按钮框架以及按下按钮相关的方法和事件。如果用户希望按钮上有一张图片,可以在按钮控件里面再嵌入一个图片控件;如果希望按钮上再有一行文字,那就在按钮中再放入一个文本框控件;如果还需要其它功能,再放入其它相应控件即可。LabVIEW中的控件不能嵌套使用,LabVIEW提供了带图片的按钮和带文字的按钮,如果需要其它功能,那只能去学习复杂的XControl了。界面常用到一种控件:列表。比如说一张列表有两列,第一列的每个元素都是一个按钮,第二列每个元素是一个下拉框。使用WPF编程非常容易,把按钮和下拉框嵌入列表控件就行了呗;LabVIEW编程可就复杂了,要自己编写程序把个按钮和下拉框控件在界面上移来移去,让它看上去好像在列表框里一样。
LabVIEW中有唯一和WPF控件这种性质类似的控件:Tab。你可以把一个按钮或者灯泡之类的控件放到某个Tab页上去,组合起来使用。

《我和LabVIEW》首页