我们已经知道:过程化编程要求程序员必须要知道程序要完成什么,并且告诉计算机如何来进行所需的计算工作,包括每个细节操作。简言之,就是将计算机看作一个善始善终服从 命令的装置。 作为LabVIEW的实践者,此阶段可能已经完成了许多图形化代码的程序设计工作,因为我们认为本书的读者对LabVIEW已经有了充分的了解。但是,面对那些已经完成 的程序设计,大家可能根本无法感觉到我们在设计中采用了什么样的编程范式。那么图形化语言如何实现过程化编程的呢?
1.1 图形化语言过程化编程的表示方法 回顾第3章的部分内容,我们就不难发现:基于数据流编程思想的LabVIEW图形化编程语言中,最基本、最广泛使用的编程范式应该就是过程化(命令式)编程范式。这点对 于学习过和使用过这种编程语言的朋友们来讲,我深信,应该是非常好理解的。
在基于数据流的LabVIEW编程思想中,我们所强调的数据依赖性和充分利用公共线程必将导致其程序结构都是基于过程化的。所以采用过程化编程范式是LabVIEW图形 化语言最基本、最显著的特征,是我们必须自觉、不自觉采用的基本编程方式。特别是在仪器控制和数据采集过程中,由于图形化语言的自身的点,这种过程化编程范式显得更自然、 更容易理解。
下面我们一起来看几个简单的实例。 例1.1-1 无相移滤波器
这个实例来自于LabVIEW2010(Mac版)自带的例程(Zero Phase Filtering.vi)。这个例子就是依赖数据关系的过程化编程。 图1.1-1 无相移滤波器实例 从工程应用的角度看,通常所设计的滤波器的滤波结果对输入信号都会产生一定的附加相移,有时候这个附加的相移是我们不希望出现的,而本例提供了实现无相移的滤波器。这个 应用在工程上是很实用的,通过这个实例可以加深对这个内置vi的理解。 例1.1-2 利用公共线程的过程化编程 图 1.1-2 利用公共线程的过程化编程——34401DMM测量温度(RTD) 在LabVIEW程序中利用公共线程实现过程化编程的实例很多,第3章中所描述的内容都与此相关,这里就不一一列举了。
其实,绝大多数的LabVIEW程序设计都是基于过程化编程实现的。或者说过程化编程是LabVIEW中最主要、最基本的编程范式。
1.2 图形化语言过程化编程的基本特点 图形化语言过程化编程基本的特点: 图形化语言最基本的编程范式
毋庸置疑,对于图形化程序设计,只要你贯彻数据流的编程思想一些程序自然都是基于过程化处理的。这样的实例我们见到很多,并且也做过许多这样的程序设计。其实,过程化编 程并不局限于上面所介绍的数据依赖性、利用公共线程等等方面。比如:顺序结构、状态机、生产者和消费者循环等所展现的程序代码也都属于过程化编程。
这种过程化程序设计的自然性根本无须特别的强调,依据数据流的编程思想人们便会在程序设计中自觉、不自觉的采用这种程序设计方式(过程式编程)。
简略的回顾一下,LabVIEW从诞生到现在的所有版本,一直都以过程化编程为最基本的编程范式,因为这些已经贯穿在LabVIEW的编程思想中。
在测试、测量任务中,过程化的处理方式也是最基本操作模式之一。 自上而下还是自下而上的编程
通常的程序设计有两种基本路线,自上而下和自下而上。基于文本的过程化编程,更强调“自上而下”的设计方式。图形化的过程化编程即可以采用 “自上而下”的设计方法,也可以采用 “自下而上”的设计方法,当然还可以同时采用这两种设计方法同时进行设计。这些仅仅取决于问题的表述和你的设计习惯。
在通用应用程序设计时,我们常采用事件驱动VI Server或状态机来控制程序的运行(依据GUI的操作),开始时我们并不需要为每个状态都填写代码,只要状态运行机制正确就可以。确定状态运行正确后,我们开始对不 同状态撰写程序代码,并且这些代码可以分配给多人分别撰写。 在测试应用程序设计时,我们通常先设计、实现基本测试、分析功能,当这部分工作正常后在开始设计其它的部分,如报告生成等。
实际上,我们习惯于先对涉及到硬件部分(数据采集、仪器控制等)的程序进行整体设计(包括仿真试验),最后综合设计完成其它的部分。
模块化设计
过程化编程的关键是将待解问题分解为自己可以理解的模块。在LabVIEW中模块是以子vi的形式出现的,每个子vi处理一个或多个任务。而子vi更优异的特性是在于可 重用。充分利用子vi的设计,实现程序代码的直观可读性和简洁性。
模块化设计子vi是一个非常好的选择,考虑到vi的可重复使用,采用通用式设计是一个有效的解决方案。比如:我们有许多NI的数据采集硬件产品,实际上在做数据采集时可 能会根据测试任务选择不同的硬件。事先,我们可以设计一个标准的vi,实现对不同硬件的通道配置。实现的方法很简单,一个枚举常量、一个Case结构外加不同硬件的配置代 码。参见下图。 1.2-1 模块化的硬件配置 这是一个有效的模块化设计的解决方案。由于NI DAQ产品配置与总线(PCI、PXI、USB、cDAQ)无关,所以它适用于大多数硬件配置。
它的最大好处是增加新的硬件很方便、很容易,仅仅需要添加一个枚举常数和Case结构中新硬件的配置代码。
当然,尽管这种方式提高了vi的重用性,但是同时也会增加vi所使用的内存空间或磁盘空间,包括也增加了vi导入时所需的执行时间。但这些对现代计算机的性能来讲几乎没 有什么负面的影响。 如果你注意到:LabVIEW2010的新特性,对模块化的设计方法使用内存过多的说法就应该不存在任何顾虑。因为LabVIEW2010对编译器进行了优化设计,使得 vi经过编译后的执行代码会更简单、更直接。这样讲,似乎很难理解,我们针对 4.3.1.2-1 做简单的说明。
在 1.2-1中,我们针对不同的硬件设计了不同的配置代码,目的是用模块化的设计方法提高vi的可重用性。实际上,在程序运行中我们仅仅用到了一种硬件的配置代码 ,也就是由枚举常数所定义的那个Case结构中的硬件,其它Case结构中的硬件配置代码根本没有使用到。LabVIEW2010编译器对此做了处理,在生成的可执行代码 中去掉了其它Case结构中根本没有用到的硬件配置代码,近而提高了程序的执行效率。并且,编译本身并不会破坏vi的源代码。
请注意,LabVIEW 2010的这个新特性是利用增加编译的时间来换得程序执行时间的降低,从而提高了编译后程序执行代码的执行速度。
2.1 LabVIEW图形化语言的事件驱动编程 需要提醒大家的是:本节所要讨论的是图形化语言事件驱动的编程范式。但我们不得不先介绍一些有关事件编程的其它知识。 我们知道:LabVIEW 6.1推出了基于事件驱动的编程方法。但是,在LabVIEW较早期的版本中,如LabVIEW 6,就已经包含有基于事件通知的编程方法(应该说是一种基于软件的同步机制)。为了更好的说明事件驱动的编程机制,这里先简单介绍一下这部分相关内容。 事件通知是一种同步机制,它允许LabVIEW程序中并行处理部件之间在事件发生时进行相互通知。由于它采用的是利用软件触发的方式发出事件通知,这样就避免了使用轮询 技术所导致的系统开销过大的问题。 在程序设计中,可以利用一个事件源(Generate Occurrence)来通知多个等待事件的用户(Wait on Occurrence),从而起到同步触发的作用。 在函数选板上可以看到这些基于事件通知的内置函数。参见下图。 图 2-1 基于事件通知的内置函数 Occurrence一词的意思是发生(事件),它与event是同义词。 在许多教科书中我们可以看到Occurrence函数的一些介绍。但是,在实际的程序范例中,我们真的很少看到使用Occurrence函数的程序(至少我的感觉是这样 )。可是在OpenG的vi包中,我们确看到了它的实际应用。这就是它的:OpenG_Wait(ms).vi。 图 2-2 OpenG Wait(ms).vi 它是用LabVIEW_Wait(ms)函数创建的vi,但比LabVIEW_Wait(ms)函数功能更强大。下面就是它的程序框图。 图 2-3 OpenG_Wait(ms).vi程序框图——Case=True 图 2-4 OpenG_Wait(ms).vi程序框图——Case=False 它不仅有错误簇输入、输出;还可以设定出现错误时是否停止定时;同时还有一个事件通知的信号输入端。前面几个特点我们这里不谈了(在《LabVIEW——北方客栈》的“ vi design”和“OpenG”专栏中已经评述过),这里仅谈谈事件通知这个特点。
众所周知,如果在一个While循环中放置一个长时间的定时器,比如60s,当用户试图使用条件终端停止这个While时,有可能需要等待很长时间(最长120s,为什 么?)。参见下面的程序框图。 图 2-5 LabVIEW_Wait(ms)函数 这绝对是一件非常令人非常沮丧的事情。因为我们在停止定时后,真的不希望存在过长的等待时间。
现在来回答为什么可能要等待上120s的时间。
因为While循环首先要执行60s的定时循环(初始条件端为“F”),即便是我们在定时开始后马上按下了停止按键,定时循环也要执行完这个定时。在完成这个定时后,条 件端检测到停止按键按下后(条件端为“T”),还要在进行一个循环才能结束。所以,最坏的情况下有可能需要等待120s时间。 现在,使用OpenG_Wait(ms).vi就可以解决这个问题,它利用了Occurrence事件通知来消除过长的等待时间。具体的程序框图请参见下图。 图 2-6 利用Occurrence事件通知来立即停止定时循环(Case结构假为空) 图 2-6最外面的循环是60s的定时循环,内部是采用Occurrence事件来停止定时的循环。这样我们就可以在任何时候都可以立即停止主定时循环。 其实仅仅使用Occurrence事件通知机制同样可以实现同样的定时功能(不使用Wait(ms)函数或vi),并且也可以立即停止定时循环。在《LabVIEW图形 编程》一书中就给出了这样的实例,我们可以一起来看看它的程序框图。 图 2-7 利用Occurrence事件通知来消除过长的等待时间(Case结构假为空) 它是利用超时值来做为定时值进行定时处理,并利用Occurrence事件通知来消除过长的等待时间。 这里请注意:基于事件通知的图形化程序代码的运行机制还是基于数据流的运行机制。其图形化代码清晰、直观、易理解。 清楚了事件通知的运行机制,现在我们该回到本节的正题,LabVIEW事件驱动编程。
2.2 图形化语言事件驱动编程的由来 我们前面曾经谈到过,计算机可视化操作系统(Mac 、Windows、Linux)的陆续出现为LabVIEW提供了广阔的生存空间和持续发展的基本环境。 在可视化操作系统中,人机对话基本上都是通过鼠标、键盘的事件响应来实现的。人们通过鼠标的拖拽、点击实现对计算机最基本的操作,计算机则通过用户事件处理机制来响应和 处理来自用户的要求。对于这些事件通常被称为:GUI事件。 尽管在LabVIEW赖以生存的基本环境中,可视化操作系统都是采用用户事件处理机制来响应和处理用户的要求。但是不知道什么原因,在LabVIEW6.1出现之前的早 期版本中,人机对话并没有相应的GUI事件处理机制。那时,对用户的操作响应采用的是轮询的方式来检测用户的操作,处理则采用队列的方式来处理用户需求。 轮询的方法有两个缺点:一是,占用CPU的资源;二是,可能遗漏用户的操作请求。 自从LabVIEW6.1推出了事件驱动处理机制后,就完全去掉了轮询的这两个缺点。使得GUI事件驱动占据了用户界面事件响应处理的主导地位。这些事件包括鼠标事件、 键盘事件、窗体事件、对象事件等等。 事件驱动机制的建立,使得LabVIEW在处理复杂事情方面能力大大的增强,可以更方便的实现用户的多种需求(不仅仅是GUI事件,还可以处理用户自定义的事件)。这样 LabVIEW也就具备了通用编程语言最基本的特性。
2.2 图形化语言事件驱动编程的表示方法
图形化的事件驱动代码参见下图。 图 2.2-1 事件结构的图形化代码 对于事件结构的基本知识介绍,这里将不做更多的讲解。比较具体的讲解和应用我们将会在下一章:《LabVIEW图形化语言的设计模式》中给出。 下面通过一个演示程序来进一步理解事件驱动编程。 例 2.2-1 事件驱动的演示程序 图2.2-2 给出了演示程序的前面版。 图 2.2-2 事件驱动演示程序的前面版 在前面版中,我们放置了三个控件。Door控件表示一个门(鼠标点击相当于敲门);Numeric控件用来纪录事件的次数(敲门的次数);Event控件用来停止演示程 序的运行。下面我们在来看看它的程序框图。 图 2.2-3 事件驱动演示程序的程序框图 这里我们创建了两个事件源,一个是:“Door Mouse Down”,用来响应和处理用户的敲门事件;另一个是:“Event Mouse Down”,用来停止这个演示程序。 “Door Mouse Down”事件处包含3个Case结构。
首次敲门,弹出对话框:“Hello!”;
第2次敲门弹出对话框:“Please do not knock it!”;
之后再敲门将弹出对话框:“I will call "110"”。
感兴趣的可下载看看! 思考题:
在图 4.3.2.2-3中的Case结构中,“Case 2”被设定为“Default”,这是为什么?运行程序试试看,选择Case0或Case1为默认Case结构会出现什么现象?
2.3 图形化语言事件驱动编程的特点
下面总结出事件驱动编程的一些基本特点 事件结构框架需与While循环配合使用 如图 4.3.2.2-1 所示,如果仅用单独事件框架,那么无论有多少个事件源、产生多少个事件,该事件结构也仅能响应最先发生的那个事件。也就是说:此时,只有一个事件会得到响应和处理。
为了能够顺利地响应多个事件发生,我们需在事件结构外面加入一个While循环。有了这个While循环,我们就可以响应和处理多次发生的事件,正如我们演示程序所示的 那样。 事件结构的停止也应采用事件处理方式 事件结构中While循环的停止,也应采用事件处理的方式完成。如演示程序中的“Event Mouse Down”事件的处理方式。也就是说,在程序执行时用布尔常数来控制While循环的条件端来停止事件结构。 图 2.2-4 停止事件也采用事件处理机制 事件结构不宜处理耗时过长的程序代码 现在我们修改上面的例子,将事件处理中的对话框去掉,换成5s中的定时。参见下图。 图 2.2-5 为每个事件处理加5s钟定时 在例 2.2-5 事件驱动的演示程序中,我们为每个事件处理程序(每个Case结构)都加入了5s中的定时。也就是说,事件处理是需要一定的时间。如果事件连续发生会出现什么样的情况呢 ?我们一同来看看。
运行该程序后,我们马上连续点击3次Door,然后再点击“Evert”。这时我们会发现,程序运行了大约15s后停止下来。
这说明,程序能够响应所有的事件,并陆续执行完所有的事件处理。事件处理时间取决于各个事件处理时间之和。所以我们建议在事件驱动机制中,事件结构中不宜处理耗时过长的 程序代码。因为,我们通常希望看到快速响应事件及快速的事件处理。
事件结构中事件处理不宜包含对话框 在图 2.2-3 事件驱动中,我们使用了对话框来处理事件的响应。实际上,在事件驱动处理程序中这种方式是不可取的、应该尽可能不用的。原因就是:对话框操作可能导致其它事件暂时无法响 应。因为,如果对话框没有被及时处理,程序会一直停止在那里等待对话框处理结果。 实际运行该程序我们会发现,在对话框弹出状态,即便我们又按动了“Evert Stop”按键, Evert Stop事件并没有被纪录下来,发生了事件丢失的现象。因为我们看到,在对话框退出后,程序并没有真正停止。也就是说,对话框实际上阻碍了其它事件的发生和响应。
2.4 图形化语言事件驱动与数据流编程之间的关系 由于事件是随时随地发生的,是不可预知的,所以事件结构本身应该讲与数据流关联性不大,它主要利用中断来响应事件的产生。但是,对事件响应后的处理(如果使用图形化语言 )还是应该遵循数据流原则。但程序处理要简洁、迅速,这点前面我们已经强调过。 事件驱动可以控制程序执行流程的改变,且又不占用、浪费CPU的资源,所以它被程序设计者广泛使用。
除了响应GUI事件外,我们还可以定义用户事件和在状态机中使用事件结构等等应用。 可以说,事件驱动的编程范式已经成为大多应用程序设计中不可缺少的一种方式。关于它的具体分析和应用我们将在下一章给出。
3.1 LabVIEW图形化语言面向对象的编程
LabVIEW在诞生之初,体现出的是一个图形化的编程语言,它的编程思想和程序运行机制都是基于数据流的。 当面向对象的编程范式风靡世界后,LabVIEW也决心将这种面向对象的编程范式引入LabvIEW之中。并从LabVIEW 8.2开始引入了面向对象的编程方式。
|