Labview学习笔记.docx
《Labview学习笔记.docx》由会员分享,可在线阅读,更多相关《Labview学习笔记.docx(84页珍藏版)》请在冰豆网上搜索。
Labview学习笔记
reference死锁问题
LabVIEW中引用经常需要和“InPlaceElementStructure”配合使用。
InPlaceElementStructure对一种引用数据进行解决时,为了保证多线程安全,它会锁住引用指向数据;其他线程若需对同一数据做操作,必要能这个InPlaceElementStructure中所有代码执行完毕才可,这样就避免了多线程读写同一内存数据所产生竞争问题。
举例来说,下面这段程序执行时间是1秒:
而下面这段程序执行时间则是2秒:
由于第二段程序中两个InPlaceElementStructure必要顺序执行。
有了“锁住”这个操作,就有不小心导致死锁也许。
例如对于同一数据引用,千万不能嵌套使用InPlaceElementStructure,否则就会死锁:
在上面这个示例中,程序运营至内层InPlaceElementStructure,就会停在这里等外层InPlaceElementStructure运营结束,释放它锁住数据;而对于外层InPlaceElementStructure来说,它内部所有代码要运营结束,它才结束。
因而它们互相等待,导致了死锁。
PackedProjectLibraries2–与Library比较
ackedProjectLibrary从名字上来看,就是被包装好了ProjectLibrary。
ProjectLibrary是编程时候由程序员创立出来。
例如下图这个工程,我在里面创立了一种叫做“MyAlgorithmLibrary.lvlib”工程库。
它包括两个VI,其中一种是私有。
PackedProjectLibrary并不是手工创立,她是通过一种项目生成规范,从ProjectLibrary编译而来。
例如上图项目,我创立了一种PackedLibrary类型生成规范。
我在这个生成规范中指定把“MyAlgorithmLibrary.lvlib”编译成PackedProjectLibrary。
编译成果是在我指定途径下生成了一种名为“MyAlgorithmLibrary.lvlibp”文献。
它后缀名仅比PackedLibrary多了一种字母p。
双击这个文献,可以打开它,看到她里面包括VI:
如果需要在其他项目中使用到这个PackedProjectLibrary,咱们可以直接把它加到另一种项目中去,下图是一种演示项目:
PackedProjectLibrary看上去和ProjectLibrary非常相似,用法也完全相似。
PackedProjectLibrary与ProjectLibrary
∙都是将功能有关一组VI封装起来办法;
∙库中VI可以具备层次机构;
∙库中VI都带有名字空间,名字空间是带有后缀名库名;
∙都可以以便放在项目管理器里使用
尽管它们十分相似,PackedProjectLibrary与ProjectLibrary相比,还是有某些明显区别:
∙PackedProjectLibrary是通过编译生成;
∙PackedProjectLibrary中VI是编译后产生,它们不能被修改;
∙PackedProjectLibrary包具有私有VI,但顾客无法看到也不能使用它们;
∙PackedProjectLibrary把VI,.lvlib以及其他用到文献都打成一种压缩包,顾客在磁盘上就只能看到一种.lvlibp文献,看不到VI文献;
∙PackedProjectLibrary很适合伙为最后产品发布给顾客使用;
∙在项目中使用PackedProjectLibrary可以缩短编译时间,由于PackedProjectLibrary中VI是已编译好,不会再随项目编译一遍。
(这一条先这样写上,但我还需要再进一步研究一下)
LabVIEW中LVClass数据转换成XML格式问题
前一段时间,一种同事程序出了问题。
她在程序中把一种LVClass类型数据转换成XML格式,再保存成文献。
但是从文献中把数据转回成LVClass时,却出了问题:
在调用“UnflattenXML”这个函数时,程序有时出错,有时又不出错。
她程序中使用了大量LVClass,并且它们之间有着复杂继承与包括关系,以至于花了两三天事件,才找出问题所在。
其实是个简朴问题,只是在设计程序时她没故意识到。
我做了一种简化程序,可以重现这个问题:
一方面,给一种子类对象设立某些数据。
然后把它当做父类类型数据,平化成XML文本,存盘:
关闭LabVIEW,然后重新打开LabVIEW。
再编写一种反向程序,把XML数据转换成父类类型数据:
发现UnflattenFromXML函数返回一种错误,value中是一种空数据。
错误产生因素如下:
在把子类数据转换成父类数据类型,这个类型虽然是父类,但其数据依然是子类。
再转换成XML格式,XML格式中记录依然是子类数据。
在反向过程中,UnflattenFromXML拿到数据是子类,但它企图转换时,却发现内存中没有子类类型信息,因而它也就不懂得如何转换这个数据,因此报错。
如果这个程序稍微改动一下,把XML数据直接转换成子类数据,就不会出错了:
事实上,子类数据总是可以用父类来表达。
因而这个XML数据亦可以直接被转换成父类类型,但前提是,一定要保证子类类型别家在到内存中去了。
只要在程序中放置一种子类对象,自然就可以把子类加载至内存。
像下面这个程序就可以正常工作:
这个实验反映出两个问题:
1.把XML中内容如果是属于某个LVClass类型数据,把这些数据转换回LVClass数据时,那个LVClass一定要已经存在于内存才行。
2.在之前一篇文章“LvClass一种效率问题”中提到过:
当子类被加载如内存时,它所有父类也会被加载入内存。
但反过来并不成立。
由于一种类有哪些父类是拟定,父类地址就记录在子类中。
但一种类并不懂得她会有多少子类,任何人都可以从它派生出不同子类来,因而它在装入内存时,不也许把自己子类也都装进来。
LabVIEW中实现链表、树等数据构造
LabVIEW自带数据构造只有数组和队列。
多数状况下,这两种数据构造足够开发者使用了。
但是,我平时使用C++和C#语言更多某些,因此编写程序时经常会想到使用其他编程语言中常用数据构造例如链表(List)、树(Tree)等。
LabVIEW中也可以编程实现这些数据构造,一种比较直观易懂编程办法是基于LabVIEW中类和引用来实现各类数据构造。
我在《我和LabVIEW》一书第13.3.5节中简介了一种简朴链表容器实现办法,它是基于LvClass编写,数据流驱动一种容器。
但是正如我在书中提到,它虽然和有某些和文本编程语言中链表相类似地方,但本质并不相似。
文本编程语言中链表,树等数据构造离不开引用(或指针),节点之间是通过引用来互有关联。
LabVIEW可觉得数据创立引用,因而也可以以便实现与文本语言中功能相似数据构造。
这里插一段,简介一下数据构造和数据容器关系,我自己理解是这样:
数据构造侧重于数据存储方式,例如如何排序;数据构造在加上与此构造有关操作办法,例如添加删除数据等办法,就构成了一种数据容器。
脱离了操作办法,单纯数据构造用处非常有限。
因而,我文章中在提到数据构造或者数据容器时,指都是同一回事:
数据构造和有关办法。
为了简介如何在LabVIEW中实现一种数据构造,我打算以双向链表为例,解说一下如何编写它。
双向链表中每个节点都会记录上一种节点和下一种节点位置。
因而,在双向链表中,可以从一种节点直接跳转到它上一种或下一种节点上去,也就是正向或反向遍历整个链表。
可以直观想到,使用LvClass实现这样节点,只要为这个节点创立一种类ListNode,并且这个类有两个成员变量,它们类型都是ListNode引用,分别用于指向前一种和后一种节点就可以了:
这样设计在文本编程语言中是没有问题,但在LabVIEW中行不通。
其他编程语言中,程序运营时,才会对类对象进行初始化。
LabVIEW中,VI一打开,它上面控件和常量就需要被初始化了。
某个对象在初始化时,它成员变量也要被初始化,若它成员变量类型还是这个类,这以初始化过程就陷入了死锁:
类需要它成员变量先初始化;它成员变量需要这个类先初始化。
基于同样因素,一种类成员变量数据类型也不可以是这个类子类:
子类初始化需要先对它父类进行初始化。
但是,一种类成员变量数据类型可以是这个类父类:
父类在初始化时候,不需要理睬它任何子类。
既然父类初始化时,不依赖于子类初始化;而子类对象又可以被当做父类类型来保存,咱们就可以运用这一特性在LabVIEW中实现可以数据构造节点了。
只但是LabVIEW实现链表节点要多一种环节:
咱们需要为ListNode类再定义一种父类ListNodeVirtual。
这个父类不做任何实质性工作,它仅用于保存相邻节点引用。
以上两个类是针对链表节点双向链表自身也需要做成一种类:
DoubleLinkedList类,这个类中封装有链表属性和办法。
例如它需要一种指向链表表头引用,需要有为链表添加删除数据办法,为遍历链表中数据,还需要有一种迭代器……
作为演示,我只实现了链表几种简朴功能。
演示程序工程构造如下:
ListNode成员变量涉及一种数据,和两个指向先后节点引用:
DoubleLinkedList类成员变量涉及指向链表头节点引用,迭代器指向节点引用,并记录了链表长度
下面看一下链表中几种重要办法是如何实现。
一方面是AppendafterEnumerator.vi这个办法,它是链表里最复杂一种办法。
它输入是链表中一种新节点,它把这个新节点添加在链表迭代器指向那个节点背面。
在给链表添加数据时,会遇到两种状况。
一方面,这个链表是一种空链表,那么被添加节点就是这个链表首节点,链表迭代器也应当指向这一唯一节点。
我设计这个链表是一种环状链表。
当链表中只有一种节点时候,这个链表上一种和下一种节点都是它自己。
如果链表不是空,就把新节点插在迭代器指向节点背面。
因而:
新节点前一节点指向应当是迭代器指向那个节点;新节点后一节点是迭代器指向节点本来后一节点。
迭代器指向节点新后一节点应当是这个新节点;本来迭代器后一节点前一节点也应当换成这个新节点。
最后,我把迭代器也指向了这个新节点,这样持续添加新节点时,它们会按照先后顺序插入链表。
我演示程序还用到了其他几种办法。
ResetEnumerator.vi负责把迭代器复位,也就是指向链表头节点:
EnumeratorgoNext.vi用于让迭代器向后移动一种节点:
EnumeratorValue.vi返回迭代器指向那个节点:
使用这几种办法就可以搭建出一种简朴演示程序来看一下链表如何工作了。
下面这个演示程序中,分两某些:
第一某些是左面那个循环,每次循环迭代就会创立出一种新ListNode对象,它数值是当前迭代次数;右半某些使用链表迭代器遍历链表中节点。
在这个演示程序中,迭代器移动次数比链表长度多了两次,由于链表是环状,转着圈访问,链表中头两个元素会被读出两遍。
程序运营后,data显示了迭代器每一步所指向节点值:
回调VI
LabVIEW界面程序最惯用构造就是循环事件构造。
用事件构造截获顾客在界面上对控件操作,然后做出相应解决。
在文本语言中,惯用事件解决办法与LabVIEW是不同。
文本语言经常使用回调函数来解决界面事件。
例如:
某个按钮按下时,需要做一种fft运算。
那么就写一段函数来完毕这个fft运算,再把这个函数与按钮按下事件关联起来。
开发语言普通已经做好了对事件监控,一旦发现按钮按下事件产生了,就去调用与它关联fft运算函数。
这个有开发者编写,被系统调用函数就叫做回调函数。
LabVIEW也可以采用与文本语言相类似办法来解决事件:
不是在事件构造内解决,而是在程序开始时,就为某事件注册一种回调VI。
在回调VI内编写相应代码,一旦事件发生,这段代码就会被执行。
与事件构造相比,回调VI编写起来稍微麻烦一点;但它好处是,它和主VI是平行运营。
如果事件解决过程比较耗时,把它放在事件构造中会阻塞整个程序,使得程序界面暂时失去响应;而把它放在回调VI中,则不会影响程序其他某些运营。
例如下面这个例子。
程序界面上有两个仪表盘:
左面那个始终在运转,每10秒钟旋转一圈;右边那个,由按钮控制,按下按钮才旋转一圈。
若把旋转右表这个工作放到事件构造按钮按下解决分支中去做,它势必会打断左表旋转,因而,考虑把它放到回调VI中去做。
这是主程序界面:
两个表盘,和一种控制右表旋转按钮。
程序代码也比较简朴。
先看代码右半部份:
这是一种典型循环事件构造,用来控制左表旋转。
但是注意,右表控制并不是在这个构造中实现。
再看程序左半某些:
它为按钮“右表旋转一圈”值变化事件注册了一种回调VI。
注册回调VI用是节点“RegisterEventCallback”,它在函数选板“Connectivity–>ActiveX”上。
这个节点重要是为了给ActiveX、.NET控件事件注册回调VI。
事件构造无法截获ActiveX、.NET控件事件,因而只能通过回调VI方式来解决这些控件事件。
但是这个节点也可以用于给LabVIEW自带控件注册回调VI。
注册回调VI节点,有三个输入参数从上至下分别是:
事件发出者、回调VI、顾客自定义数据。
在咱们这个例子中,需要截获是按钮“右表旋转一圈”值变化事件,因而需要把“右表旋转一圈”控件引用作为第一种参数传递给注册回调VI节点。
指定好事件发出者,接下来需要选取事件类型,鼠标点击注册回调VI节点第一种参数接线方块,发现“右表旋转一圈”按钮所有事件都已经列在这里了,选取“值变化”事件。
第三个参数是顾客自定义数据,可以是任意类型数据,在回调VI中需要用到数据都可以通过它来传递。
由于我打算在回调VI中对控件“右表”做修改,因而,在这里把“右表”引用作为数据传递给回调VI。
第二个参数是回调VI引用,如果已经写好了回调VI,把引用传进去就行了。
我还没有编写回调VI,因而可以在参数接线端上点击鼠标右键,选取“CreateCallbackVI”创立一种空白回调VI。
回调VI中写一小段代码,让右表旋转一圈,整个程序就完毕了。
这时,左右表可以各自运营,互不影响。
LvClass一种效率问题
前几天,听到了一种客户抱怨:
她编写了一种LabVIEW程序,每次打开主程序就要耗费几分钟时间,这有点令她忍无可忍。
我没有见过她源程序,但是据帮她检查过程序同事讲,她问题很也许是使用了大量LvClass导致。
在她项目中,包具有上百个类(LvClass)。
我此前也据说过LvClass在效率上也许会有些问题,听到了这个消息后,我自己做了一种实验。
LabVIEWScripting中有一种属性节点可以用来查看内存中所有VI,我就运用这个VI来查看一种程序究竟在装入些什么,令它启动如此之慢。
假设不存在子VI,如果打开某个不在LvClass中VI(即便这个VI是属于某个lvlib),只有这个VI会被装入内存。
但是,打开某一种LvClass中VI,我发现不但这个VI会被装入内存,它所在类中所有其他VI也都被调入内存。
如果这个类尚有父类和祖先类,那么所有父类、祖先类中VI统统都会被调入内存。
总结一下就是这样:
当一种VI被装入内存
1.它所有子VI都会被装入内存;
2.它所在类中所有VI都会被装入内存;
3.它所在类父类中所有VI都会被装入内存。
以上3条可以是递归发生,例如一种主VIA被装入内存,它子VIB也会被装入内存,和B同属一种类VIC也要被装入内存,C中有个子VID,D属于类E,E有个父类F,F中有个办法VIG。
尽管G功能和程序A八杆子都打不着了,但也会被装进来。
这大概就是那个顾客遇到问题,表面上她程序不算太大,但是程序开始启动时,却需要把多于程序自身数倍不有关VI都装入内存,这一过程会每次都挥霍她几分钟时间。
鉴于LvClass这一特性,设计使用它时候一定要格外小心,否则很也许会导致程序效率低下。
我想到了几点需要注意地方:
1.如果仅需要对某些VI进行封装,那么应当使用lvlib,而不是lvclass。
两者封装重要区别是,lvclass可以封装对象属性(也就是模块用到数据)。
2.类中VI必要是高内聚,类中办法共同完毕某一基本功能,不可再分割。
应用程序一旦用到这个类中某个VI,就意味着程序将会使用到类中几乎所有VI;而不是一种应用程序也许只使用这个类中某几种VI。
3.继承关系应当尽量简朴。
没有必要时候尽量不使用继承。
LabVIEW不支持接口,不应创立一种纯虚类,然后当作接口来用。
4.尽量不要嵌套调用。
例如在一种类VI中又去调用另一种类中VI。
5.打算使用多态这个特性时要注意,多态使得应用程序在运营时,依照对象类型选取相应解决办法。
但有些选取应当是程序编译时就做出,它们不适合套用在多态特性上。
举某些例子:
∙INI文献读写这个模块比较适合做成类,每个INI文献相应一种类实例。
它有丰富数据(文献内容);它办法有限,基本上只需要打开、读条目、写条目、保存关闭,这四个办法,并且普通应用程序都会同步使用到这四个办法。
∙复杂仪器驱动程序不适合做成类。
由于驱动程序会提供非常多功能,示波器有各种触发模式。
而一种应用程序普通只用到各种模式中某一种就够用了。
∙某测试程序可以生成测试报告给顾客。
顾客可以选取几种不同报告类型。
生成报告模块可以用lvclass来设计。
由于生成不同类型报告办法间,可重用代码诸多,可觉得它们设计一种基类。
并且,是程序运营时,才选取生成报告类型。
∙某一测试程序,可以支持各种型号仪器。
由于不同顾客使用不同硬件。
对不同型号仪器支持不适合使用lvclass来设计,由于测试程序发布给顾客时,顾客硬件设备是固定。
对仪器选取应当是程序发布时就决定好,而不应等到程序每次运营起来后判断。
如何在程序中同步弹出各种子VI界面,各自运营互不影响
回答网友一种问题:
“我设计了一种labview界面子VI,我想在主VI中多次调用该界面VI(同步执行,单独分派内存),并显示出多窗口,该如何设立?
我尝试将子VI属性设立成可重入,仍无法解决。
”
这个问题其实挺常用。
若需要子VI打开多份实例,子VI必要是可重入。
因此第一步要把子VI设立为可重入。
但仅仅这样还不够,主程序运营到子VI处,把子VI打开后,会始终等在这里,懂得子VI运营结束,才继续执行主VI后续代码。
主VI既然已经停在这里了,自然不会再继续去打开其他子VI。
解决办法是在调用子VI地方,改为动态调用,并且不等待子VI运营结束。
这样一来,主程序运营到这里,将子VI调起后,及时执行后续代码,又可以去调用其他子VI了。
需要注意是,用于子VI是可重入,需要给“OpenVIReference”函数设立一种值为“8”Options参数。
主VI程序代码如下:
LabVIEW新功能–传引用
此前版本LabVIEW虽然也有各种办法可以让数据以引用方式在程序间传递,但是用起来均有些麻烦。
LabVIEW有了构建数据传引用节点,大大简化了传引用程序代码。
新添关于传引用两个节点在函数选板“Programing->ApplicationControl->MemoryControl”中,分别是“NewDataValueReference”和“DeleteDataValueReference”。
“NewDataValueReference”用于创立一种数据引用,“DeleteDataValueReference”可以从引用中取回本来数据。
引用最重要应用于多线程程序中。
如果两个线程同步对同一份数据进行修改,则必要使用传引用机制。
否则,使用值传递方式,数据在数据线分叉地方,就会编程独立两份,之后在两个线程内分别修改是两份完全独立数据,没办法对同一份数据进行修改。
例如下图这个程序,程序输入了一种数组,然后需要在两个并行子VI中同步对这个数组中数据进行修改。
每个子VI也许修改了数组不同元素,程序运营结束产生数组应当把两个子VI中修改都包括进来。
因而,程序一开始需要数组数据生成一种引用,然后把引用分别传递到两个子VI中去。
两个子VI都运营结束后,在从引用中取回数据。
LabVIEW中已有函数还都是为值传递设计,因此使用值传递少不了把数据取出、放回过程。
这以过程中,也许又会产生数据拷贝,效率会比较差。
好在咱们可以使用“InPlaceElementStructure”构造来解决从引用中取出、放回数据过程。
配合了“InPlaceElementStructure”构造使用后,LabVIEW会尽量使用数组原地址,而不是把从引用中取出数据复制一份,这样就做到了传引用与效率兼顾。
例如下面两图中程序,功能是完全相似,但LabVIEW会对下面一幅图中程序进行优化,提高效率。
美化程序–隐藏程序框图上大个Cluster
在编写某些程序时候也许会遇到如图1所示情形:
即用到了一种极为复杂数据类型常量。
这个常量由于体积巨大,使得在程序框图无论怎么摆放都让人看起来不太舒服。
如何才干把这个程序改造得美观某些呢?
图1:
体积巨大常量会有碍观瞻
要解决这个问题,只有设法把这个常量在主程序框图上隐藏起来。
普通可以用如下两种办法。
第一种办法:
把这个常数变换成控件,再把控件隐藏起来。
这种办法比较简朴,但是也有弊病。
①容易引起误解:
控件普通表达有值传入,其她人读程序读到这里就也许搞不清晰这个值是从哪里传来了;②如果要修改常量Cluster中某一种元素值,操作起来比较麻烦。
第二种办法,也就是我向人们推荐:
把它隐藏到更深层子VI中去。
详细操作办法如下:
如图2先给这个复杂数据类型建立一种StrictTypeDef。
我建议是为所有程序中用到Cluster都建立一种StrictTypeDef。
这样可觉得后来程序维护省去诸多麻烦。
图2:
StrictTypeDef.
然后然后再建立一种新VI,把咱们要隐藏这个个头巨大常量摆放在这个VI中,并且连接一种Indicator,以把它值传出来。
VI接线板采用4-2-2-4格式,最下层第3个接线端用于传出VI中唯一数据,如图3所示。
图3:
用于隐藏个头巨大常量VI
这个VI图标要做得小巧美丽,如图4,图标不一定非要做成正方形。
只要B&W和256Colors中图标形状同样,咱们就可以画出不规则图标了。
详细办法可以参照《制作不规则图形子VI图标》。
图4:
常量数据VI图标
把这个新造出来常量数据VI拖到程序框图上,把它输出链接到刚才链接常量地方,再把位置摆放好。
当前咱们程序是不是美丽多了
图5:
改造后程序框图
Caption和Label书写规范
LabVIEW控件Caption和Label特性和用途很相似,都是给了控件一种故意义名字。
因而,在诸多场合没有必要刻意区别她们。
Caption和Label最重要区别在于,Caption可以在程序运营时候变化;而Lab