Windows Desktop Search 编程

    最近,抽空研究了一下 Windows Desktop Search 编程,发现这东西真不咋样。最主要的是资源太少,估计没几个人编程的时候用到这东西的。网上搜了一下,几乎没有程序员写的相关文章。有个官方论坛,发言者寥寥无几。微软自己提供的相关文档散落在MSDN的各个角落,不成系统,看起来也挺费劲的。而且很多文章是几年前写的,到现在链接都失效了。

    简单总结一下就是这东西终端用户用用还行,做二次开发就不太适用了。微软的捆绑销售策略虽然恶心,但确实管用。否则有Google在,谁用他的破搜索啊。

广告

谷歌金山词霸

    最近一年一直用的是有道词典,周围的人也渐渐放弃了金山词霸。本来以为金山词霸开始没落了,谁知它最近跟 Google 合作,搞出来一个谷歌金山词霸,大有中兴之势。

    以前金山词霸的缺点是词库太死板、收费;而有道词典屏幕抓词功能又太差劲(尤其对于 Lotus Notes 这款我每天必用的软件,需要查单词最多的地方就是这里了,道词偏偏就无法在其中取词)。现在好了谷歌金山词霸集合了两家优点:屏幕取词功能还是原来金山词霸的、加入了 Google 的网路词典、Google 垫付了所有用户的钱,谷歌金山词霸成了免费软件了。

    几天试用下来谷歌金山词霸在网络释义和例句方面都不及有道词典,不过多数情况够用了。于是我现在放弃有道,改用谷歌金山词霸了。偶尔有谷歌金山词霸解释的不好的词语,还要到有道的主页上去查一查。

英语学习》目录

全局变量

    全局变量是一种数据在不同节点,不同VI,不同线程间传递的方式。数据被保存在某一固定的内存空间里,不随数据线流动。在需要读写数据的地方,不需要外部链接的数据线,直接通过某些节点或VI就可以得到目标数据,并对其操作。
    在LabVIEW 中应当尽量避免使用全局变量。全局变量看似方便,但带来的问题也很多。最主要的是它破坏了数据流顺序的逻辑关系,导致程序可读性和可维护性下降。
    偶尔也有不得不使用全局变量或使用它利大于弊的情况。比如:实现子 VI 间参数传引用的机制;在不破坏程序可读性的前提下,避免一些过于杂乱的数据连线;对高层用户隐藏某些底层模块内部使用的数据。

一、全局变量(Global Variable)

   

此处所说的全局变量是特指图标像地球的那个 Global Variable VI。使用这种全局变量,目标数据被存放在一个只有前面板的特殊VI中,任何需要使用这个数据的地方,把它所在的 Global VI 拖过来即可。如同前面所述,全局变量虽然使用方便,但是缺点也十分明显。
    首先,它不利于代码的可读性,破坏了数据流顺序的逻辑关系。使用全局变量难以知道数据是否在其它地方被改动过。换言之,代码上的全局变量,不能直观的反映出它的数据来源。
    其次,它的安全性低。全局变量可以在任何地方被直接读写。即便知道数据在某些地方不应该被改动,也无法对其进行控制。
    再次,它的效率低下。VI每次读全局变量,LabVIEW 都要为读到的数据复制一个新的副本,这毫无疑问影响到VI的效率。
    此外,全局变量的不合理使用还可能导致竞争状态。比如下图中的VI,假设全局变量 Data 的值原本为 0,运行完下面这个加2减1后的代码后,Data 中的值是几呢?可能是1,也有可能是2,还可能是-1,这完全取决于程序的执行顺序。而在这种情况下,这个顺序是不确定的。


图1:处于竞争状态的全局变量

二、单进程共享变量(Single-Process Shared Variable)

    共享变量有三种:单进程,网络发布,以及时间触发的共享变量。后两种主要应用于不同硬件设备、不同计算机、不同进程程序间的数据交换。在此,我们仅仅介绍与全局变量相关的第一种:单进程共享变量。共享变量的种类可以在它的属性页中进行修改。
    单进程共享变量,顾名思义就是作用域为单个程序进程的共享变量。它与全局变量的性质是完全相同的。唯一的不同点是单进程共享变量带错误输入/输出端,我们可以利用错误处理连线来控制单进程共享变量的执行顺序。比如下图中的VI,假设共享变量 Data 的值原本为 0,运行完下面这个加2减1后的代码后,Data 的值必然为1。


图2:共享变量的使用

    这并不意味着单进程共享变量可以防止出现竞争状态。设想上图的VI只是程序中的一个子VI,在其运行的同时,Data 仍然可以在其它子VI中被访问,因此,仍然有可能处于竞争状态。
    共享变量是 LabVIEW 8 之后的一个新东西,比必须被保存在某个 Library 内部,不能独立存在。

三、功能全局变量(Functional Global)

    功能全局变量与前两种全局变量完全不同,它在 LabVIEW 中就是一个普通的 VI。它把需要在全局使用的数据保存在一个没有初始化的移位寄存器中,并实现相关的访问这些数据的方法。
    功能全局变量的代码结构都是类似的:主体是一个只执行一次的循环结构;内套一个选择结构;一个输入用于选择某种操作;若干用于输入和输出数据的控件。
    其实,使用循环结构仅是为了利用它的移位寄存器。移位寄存器没有连初始化数据,因此每次执行这个VI时,它里面保存的是上一次 VI 执行结束时的数据。这样,就可以在程序的全程保存、处理或使用这一数据了。功能全局变量 VI 不可以被设置为可重入,否则在不同地方,得到的移位寄存器中的数据就不是同一份了。
    从 LabVIEW 8.5 开始,VI 的程序框图增加了反馈节点了。可以使用它来替代仅执行一次的循环,以简化程序。下面两图就是完全等效的功能全局变量。


图3:实现加减法功能的功能全局变量


图4:等效的加减法功能全局变量

    与前两种全局变量相比,功能全局变量有两项主要的优点。所以我建议,如果不得不使用全局变量,那就使用功能全局变量。
    首先,功能全局变量可以防止竞争状态出现。因为功能全局变量的VI是不可重入的,所以把它作为子 VI 时,绝对不可能出现两个相同功能变量子 VI 同时执行的情况。因为对全局数据的所有操作都是在这个 VI 内部完成的,也就意味着,所有对数据的操作都绝对不会被其它操作干扰。图5中的VI,执行结束必然导致全局数据增加1,即便还有其它线程的子VI在同时运行也不会影响这个结果。但需要注意的是,解决了竞争状态不等于全局变量的使用顺序可以乱写,随机的顺序很可能导致错误。设计功能全局变量时可以加入出错处理的连线,以方便使用时确定全局变量的调用顺序。


图5:功能全局变量的使用

    另一优点是,功能全局变量可以封装内部数据、控制对数据的访问权限。例如图3 所示的那个功能全局变量,故意没有设置数据的方法。因此,使用这个全局变量的高层程序是无法直接修改全局变量的值的,只能使用给定的方法:复位或加减。甚至也可以不对高层程序提供直接查看全局数据的方法,只允许通过某些方法得到数据处理后的结果。这样全局数据就被很好的隔离开来,避免了被不当改动的风险。

    由于功能全局变量的这两个优点,它一度受到程序员的极力推崇。我读过一本名为《A Software Engineering Approach to LabVIEW》的书,它的核心思想就是建议读者把程序模块全部写成功能全局变量的形式。
    功能全局变量虽然有上述优点,但用它来作为较大功能模块的框架,还是存在很多不足的。首先,这种实现方法,模块最主要的功能和代码都在同一个VI中实现。这个VI会变得十分复杂,难以维护。其次,模块如果接口的数据较多,这个VI的连线就会极为复杂。再有,如果模块要增加或改动点什么功能,这个大VI参数可能会发生变动,引起版本不兼容的问题。
    LabVIEW 从8.2 版本起,已经支持面向对象的编程,类的概念发挥了功能全局变量的优点,克服了其缺点。因此,再设计功能模块应该首先考虑 使用LvClass。另外,利用LVOOP 可以设计出更便于维护的功能全局变量,不过这更加复杂,我们以后在介绍LVOOP的时候一并介绍吧。

下载功能全局变量的示例

《我和LabVIEW》目录

面向对象与数据流驱动的结合

    LabVIEW 是数据流驱动的编程语言:数据在数据线上流动,每个节点通过输入端的连线接收到数据,对其进行处理,再把结果传给输出端连线。
    为了符合数据流的概念,多数情况下 LabVIEW 函数(或子VI)使用的传参方式是值传递:就仿佛是整个数据在连线上流动,遇到一个节点,便一股脑都传到节点中去。必要时,譬如数据线分叉的时候,数据便生成一个副本,这样就有了两份同样的数据,沿着不同的分支继续传递。
    也有例外的情况,比如使用 refnum 这种数据类型的时候。真正有意义的数据是存放在内存的某个地方不动的,而在节点间流动的只是一个指向这片数据的引用。这种传引用的方式破坏了数据流的概念,只在不得已的情况下才可使用(比如在多线程中对同一块数据进行操作),否则还是应当尽可能遵循 LabVIEW 一贯的值传递方式。

    为了与用户熟知的数据流方式兼容(风格不一致,必然造成程序的极大混乱),LabVIEW 中的对象也是按照值传递的方式在节点间流动的。这点和其它语言有所差别,文本编程语言中传递对象时,基本都使用传引用的方式。
    实际上,这两种传参方式各有特色,LabVIEW 别具一格的选择了值传递方式,是因为,在 LabVIEW 中,值传递的优势更大一些。

    传引用的优点在于效率高,一个对象的数据量往往都比较大,值传递免不了要生成一些副本给被传递的数据,这类开销是相当大的。而一个引用类型的数据一般只需要占用4或8个字节,传递它们的开销远小于直接传递数据。
    在多线程程序中,传引用意味着不同的线程可以访问同一块数据。在不同的线程中同时对同一数据进行读写是很危险的,它可能会产生不可预期的结果。所以,在多线程程序中常常使用临界区、信号量等方法来防止竞争状态的出现。这对于 C++ 或其他文本语言的程序员不是一个太大的问题,编写多线程程序的人员多少已经对可能出现的竞争状态有所了解。并且他们清楚地知道自己在编写多线程程序,会格外留意并采取相应措施防止错误出现。

    而 LabVIEW 的使用者中,相当一部分人是非计算机专业的。为了帮助这些非计算机专业编程者利用多线程的优势,LabVIEW  采用了自动多线程的机制。LabVIEW 中,编程者并不需要告诉程序去开辟新线程;任何两段逻辑上没有先后顺序的代码,都有可能被自动的放到两个线程里去同时执行。
    在这种情况下,传引用是非常危险的,编程者可能根本没意识不到程序有多个线程,因而无意中写下存在竞争状态的代码。
    只有值传递才可以解决这个问题。值传递意味着数据每到一个分叉处,就变成相等但互相独立的两份数据。每个可能同时运行的数据线上的数据都是相互独立的,程序永远不会试图去同时访问同一个数据。这样就避免了无意识下造成的竞争状态。如果程序中的确需要在不同线程里处理同一对象,编程者可以在明确程序风险的前提下使用 LabVIEW 中的传引用机制,并做好多线程安全防护。

    那么,类的实例作为值传递的数据,到底都包含哪些内容呢?这个数据你可以把它看成是一个簇。这个簇中可能又包含一下多个簇:
    1. 一个簇包含所有类的属性;
    2. 一个簇包含它父类的所有属性;
    3. 一个簇包含它父类的父类的所有属性;
    4. 一个簇包含它父类的父类的父类的……
    ……
    除了这些属性数据,类的实例还包含有自身类别的信息,比如自己属于哪个类,什么版本等。这样,一个实例即便是按照它的某个祖先类来传递,也同样可以在需要的时候调用属于自己本身类的方法。

《我和LabVIEW》目录

拔掉了最后一颗智齿

    我总共就长了三颗智齿,都没长好。前两颗早就被拔掉了,第三颗在左上,情况稍好一点,但是也有点向下颌骨和颅骨结合的部位倾斜。并且它还比别的牙齿多长出来一节,有时会挡住下牙的活动。所以这次下定决心把它拔掉。
    实际上我去年底就去了一次医院,准备拔掉它。当时去了离家很近的曙光医院东院。排队的时候,我后面一个人对着护士说:他上次来拔牙,留了个牙根在里面,现在发炎了。我一听就害怕了,早有耳闻说曙光东院的技术差,还真是名不虚传。于是也没拔这逃跑了。
    这次是去了中山医院,星期一的时候。我对这家医院的印象还不错。这颗智齿比别人长的长,也比较好拔,所以过程非常顺利,也不怎么疼。只是,当我躺在躺椅上,医生刚开始检查的时候,悼念四川地震遇难者的时间到了。窗外响起一片汽笛声,医生立刻扔下我趴到窗边看热闹去了。于是我就躺在那,张着嘴巴默哀了三分钟。

    估计过些日子还得去趟医院。我右腮帮子上长了小硬块,我自己诊断了一下,可能是腮腺结石或者涎腺结石一类的东西。下次要去医院验证一下。

LabVIEW 面向对象程序设计的简介

    LabVIEW 的数据流驱动模式,与面向过程的编程思想有些类似。它们都是把程序看成是一组过程或功能的集合,LabVIEW 利用数据流控制这些功能执行的顺序。由于开发者可以随意的修改、调用这些功能模块,在程序开发的后段,模块之间的划分会变得模糊,依赖关系也变得无序。这种方式就不再适合大型程序的开发。
    面向对象的编程思想是专为解决这个问题提出来的。面向对象的编程思想大大提高了编程时的灵活性和可维护性。现在的大型程序中几乎没有不基于面向对象编程思想的。LabVIEW 为了适应这一趋势,也从 8.2 版本开始引入了面向对象程序设计的思想。

    面向对象有三大特征:封装、继承和多态。

    封装是把高度相关的一组数据和方法组织在一起,形成一个相对独立的类。外部程序只能通过严格定义好的接口访问类所允许公开的数据和方法;而对于不需与外部发生联系的数据和方法,类会把他们隐藏和保护起来。这样就避免了编程过程中,函数模块常常被到处滥用以至于难以维护的弊病。(假如,我们的程序是模拟多只小狗的日常生活的。在设计程序时,就可以把他们抽象归为“狗”类。这个类包括了一些属性,如年龄、皮毛颜色、名字等等;还可以包含一些方法,即狗的行为,比如进食、移动、叫等。)

    初一看 LabVIEW 中的 Class 就会发现它很像 Cluster,或许它就是在 Cluster 基础上发展来的。C++ 中的 Class 也是在 Struct 的基础上发展来的,而且,在 C++ 中,除了函数默认的权限不同,Class 和 Struct 是等效的。在LabVIEW 中,二者还是截然分开的,Cluster 中只有数据,Class 中除了数据,还可以有方法。
    C++ 类中的成员变量可以是私有,也可以是共有;为了安全起见,LabVIEW 中所有的数据都是私有的,必须通过公有的VI才能访问这些数据。
    C++ 的类拥有构造函数和析构函数;LabVIEW 的类没有这两个方法。

    继承是为了鼓励代码重用。不同的类可能拥有共同属性和方法,这些共性可以被抽取出来成为父类,被所有子类继承。比如,我们的程序要模拟“狗”和“鸡”两种动物的生活。它们之间其实有很多相似之处的,作为一个优秀的程序设计方案,应该把这些共同点提取出来,构成一个父类“动物”。“动物”类具有“狗”类和“鸡”类的公共属性与方法,比如年龄、进食等。构造“狗”或“鸡”类时,首先把这些公共属性、方法继承下来,再添加一些自己独特的属性方法,比如狗“看家”、鸡“下蛋”等。
    C++ 的类支持多继承;LabVIEW 的类只支持单继承,这与 Java/C# 相似。
    LabVIEW 中所有的类都有一个共同的祖先类,而 C++ 中没有。这点也与 Java/C# 相似。

    多态最早也是个遗传学概念,源自同一祖先的不同生物会表现出多种不同形态。在面向对象中,多态是指同一个方法,在不同子类中有不同的表现方式。多态可以简化我们的编程,比如:几个子类都有同样一个继承自父类“动物”的方法“移动”,而不同的子类,狗和鸡移动的现实代码是不相同的:一个使用四两条腿,一个使用两条腿。在应用程序中只要调用父类“动物”的“移动”这个方法,一旦程序运行到这里,就会自动判断要处理的实例是属于狗还是属于鸡,然后去调用狗或鸡类中对“移动”方法的实现。
    LabVIEW 的面向对象也实现了对多态的支持。

《我和 LabVIEW》目录

结构性面试

    面试官需要多长时间可以对应聘者是否易于相处、是否积极主动、交流能力等相关特质做出预测?根据调查人员做过这样一个实验①,抽选两组人作为面试官,一组面试官对每个应聘者只面试12~15秒钟,另一组面试官对每个应聘者面试20~30分钟。再随机抽取了近百个应聘者分别接受两组面试官的面试。结果,两组面试官对应聘者的评价几乎是一致的。这个实验证实,面试官其实只需要15秒钟就对应聘者产生了第一印象,剩下的半小时面试基本不能变先前留有的第一印象。
    这个实验比较容易重现,下次面试前,先跟应聘者聊上一两句,然后就在纸上记录下上文提到的几个特质。在完成半小时面试后,对照先前所做记录,看是否需要修改。

    这个实验也说明非结构面试的低效率,半个小时完成的工作和15秒钟没有太大分别。因此,非结构性的面试其实就是在浪费时间。
    所谓非结构性面试,就是面试官询问一些主观性的问题要求应聘者回答。比如:“你如何应对压力?”,“你的组织能力如何?”等等。非结构性面试对于应聘者今后工作能力的预测是非常不准确的。这主要是由于以下两个原因造成的:
    应聘者是有备而来的。你可以判断应聘者对非结构性问题回答的好坏,但是不能判断他们答案的真假。
    非结构性面试的结果基本相当于面试官对应聘者的第一印象,正如上文实验证实的那样。第一印象是在发现了应聘者一些显著特性之后,马上把他归为与自己相识的某人一类,主观以为二者在其它方面也类似。我跟同事讨论应聘者时经常听到说这个应聘者跟我们的某某同事很像之类的议论,这就是第一印象在起作用了。而实际上二者的工作绩效可能完全不同。

    改善面试对工作绩效预测的方法之一是采用结构性面试。结构性面试要求面试官针对招聘的职位,精心设计一套可以探知应聘者以往经历的问题。问题类型应当是情景回忆式的,即询问应聘者在某些几位具体的情景下,处理问题的过程。这样做的目的是为了尽量考察应聘者以往的经历,而不是他的主观想法。毕竟,预测一个人未来的最可靠依据是他的过去。
    结构性面试也不是百分之百准确的,但是跟踪调查的结果显示结构性面试的准确率比非结构面试高两倍以上参考②。已经非常可观了。

相关文章:
    面试官的面经
    如何有效地在面试中考察应聘者的能力
    生活随笔
    ① ② 引用自《心理学》David G. Myers 第12章

调用动态链接库 6 – LabVIEW 中对 C 语言指针的处理

https://lv.qizhen.xyz/#docs/external_call_dll

C 语言函数常有指针类型的参数,有时候,在 LabVIEW 中只能得到一个指向某个数据的指针。比如,在第4节里的一个例子:

#pragma pack (1)
typedef struct { char a; char* str; int b} MyStct;
MyStct* testStruct;
long TestStructure(MyStct* tempStct);

在 LabVIEW CLN 节点中,就只能返回以整数类型表示的 str 的指针。
在大多数情况下,并不需要在 LabVIEW 中得到指针指向内存的具体数据,对这些数据的操作是在DLL的函数中完成的。我们只需在LabVIEW中得到这个指针的地址,再把它传递到下一个 CLN 节点就可以了。

但在某些情况下,我们仍然需要在 LabVIEW 中得到指针指向的内容,这只能借助 C 语言来完成。在上面的例子,我们需要另外写一个 C 函数,把函数 TestStructure 返回的 tempStct 结构中的元素拆开成简单数据类型,作为新的函数的参数(新函数中的一个参数就是 char* str,LabVIEW可以识别它)。在LabVIEW 中调用这个新的函数,可以得到这些简单数据类型的数据。
有些函数需要在外部开辟的一块内存中写入数据,LabVIEW 中没有分配内存的操作,也需要再写一个 C 的函数分配好内存,给被调用的函数使用。
这种做法的缺点是针对每个需要得到内容的指针都要做个包装函数,相当麻烦。

一个减少C代码的方法是:编写一个C函数,负责把指针指向的内存中的数据以数组的形式读出,再在 LabVIEW 中把它们从新组织成合理的数据类型。这种方法其实更复杂,好在 LabVIEW 8.5 中自带的一些 VI 已经做了这个工作。如果你需要,不需要再额外编写代码,直接用 LabVIEW 提供的 VI 就可以了。
[LabVIEW]\vi.lib\Utility\importsl\GetValueByPointer\GetValueByPointer.xnode 就是用来得到指针内容的一个VI。告诉它指针地址、数据类型,它就会返回正确的 LabVIEW 数据。参见下图中的示例:

    DLLMemory.dll:ReturnPointerToConstant 返回的是一个指针,指向我在C语言中声明的一个整数常量。把这个指针传给 GetValueByPointer.xnode 并且告诉它数据类型是I32,GetValueByPointer.xnode 就会得到这个指针指向的内容。
[LabVIEW]\vi.lib\Utility\importsl\ 中还有几个 VI 可以在调用DLL时起到帮助作用。比如,对于函数需要使用外部开辟的内存的,就可以使用 DSNewPtr.vi 开辟一块内存,然后把地址传递给这个函数。
需要注意的是,这几个 VI 不是 NI 承诺给用户使用的,所以没有什么文档,需要用户自己研究它们的用法。

《我和 LabVIEW》目录