配件定义的一般形式如下:
在关键字implementation后面的大括号内是配件实现代码。
配件的实现代码由两部分组成。第一部分是声明组件语句,用于声明配件将要使用的组件。第二部分是连接语句。在连接语句中,有一类是使用操作符“->”或“<-”将前面声明的组件连接起来,另一类是使用操作符“=”将自己声明的接口与前面某个声明组件的接口连接起来,从而实现配件声明的接口。对于顶层配件而言,其实现代码中的连接语句只有第一类。
例3.16:应用Powerup的顶层配件PowerupAppC
以上是应用Powerup的顶层配件PowerupAppC的代码。配件PowerupAppC的规范为空。配件PowerupAppC的实现代码中:行(1)是声明组件语句,声明了本配件使用的三个组件;行(2)和行(3)是第一类连接语句,将行(1)中声明的组件按照需要连接起来。具体地说:使用操作符“<-”将组件MainC与组件PowerupC通过接口Boot连接起来;使用操作符“->”将PowerupC与LedsC通过接口Leds连接起来。配件LedsC的定义如例3.18。
例3.17:配件LedsC
配件LedsC的规范中提供了接口Init和接口Leds。在配件LedsC实现代码中,除了使用操作符“->”连接组件LesP与组件PlatformLedsC外(行(4)、行(5)),还使用操作符“=”,将自己提供的接口Init(Leds)与LedsP提供的接口Init(Leds)连接起来(行(2)、行(3))。后面会解释这几个操作符的作用。
在配件的实现代码中,声明组件与C中声明某个变量类似,难点主要在于使用操作符来实现连接。
1.声明组件
在配件实现代码中声明组件时,使用关键字components,后面跟着需要声明的组件名字,如果要声明多个组件,可以使用多条语句分别声明,也可以使用一条语句一起声明,对于组件名字的先后顺序没有要求,但是要用逗号隔开,声明组件语句以分号结尾。
例如在顶层配件PowerupAppC中,行(1)使用一条语句声明了三个组件MainC、PowerupC和LedsC。使用多条语句分别声明也是可以的。
在声明组件语句中,可以使用关键字as为组件设定一个别名。在3.2.2小节中我们已经介绍过关键字as,主要讨论了如何使用关键字as为接口设定别名。这里继续介绍一下在配件实现代码中声明组件时关键字as的主要应用场合。
1)nesC组件名字位于全局范围,为了增强名字的描述性,组件名往往较长。例如,ATmega128上SPI总线的硬件表示层组件名字是HplAtm128SpiC。在配件中使用组件时,较长的名字往往为书写带来麻烦,因此,在声明时可以使用as为其重新命名。例如,在封装HplAtm128SpiC的稍上一级的配件如Atm128SpiC中,将HplAtm128C重新命名如下:
实际上,在配件中声明组件时都隐性的使用了关键字as。这与在组件规范中声明接口是一致的。上述声明组件MainC的语句实际上是下述语句的缩写。
2)关键字as的一个重要应用场景是:在多次实例化同一通用组件(通用组件在后面会介绍)时,需要不同的名字分别与各实例对应。这种场景下,可以实现区分的唯一方式是使用关键字as为其设置别名。例如,在配件BlinkAppC的实现代码中实例化通用组件TimerMilliC()得到三个实例,为了区分这三个实例,使用关键字as分别为其命名为Timer0、Timer1和Timer2。
例3.18:BlinkAppC配件
3)关键字as还有一个重要的作用,就是带有一定的间接性。具体地说就是,使用关键字as为某个组件设定一个别名,当需要替换这个组件时,只需要替换这一行,而不必在所有该组件名出现的地方都进行替换操作,减少了差错出现的可能。
例如,在配件ActiveMessageC的实现部分,使用关键字as把组件CC2420ActiveMessageC设定别名为AM。然后通过操作符“=”将组件ActiveMessageC提供的接口与组件AM(即组件CC2420ActiveMessageC)提供的接口连接起来。这样,若需要替换该组件的使用,则可以直接替换实现部分的第一行替换该组件即可。
2.连接
在nesC中用两类操作符来实现连接语句:一类是操作符“->”或“<-”,用来将接口的使用者与接口的提供者连接起来;另一类是操作符“=”,用来将配件自身提供或使用的接口与某个声明组件的接口连接起来。下面分别介绍这两类操作符。
(1)操作符“->”和“<-”
操作符“->”和“<-”是两个基本的连接操作符,用于将接口的提供者与使用者连接起来,实现调用组件和被调用组件之间的绑定。箭头从使用接口的组件(接口的使用者)指向提供接口的组件(接口的提供者)。不必区分连接的这些组件是配件还是模块。
在配件PowerupAppC中,行(2)的连接语句使用操作符“<-”将组件MainC和组件PowerupC通过接口Boot连接起来。其中,PowerupC是接口Boot的使用者,位于箭头的尾部,MainC是接口Boot的提供者,位于箭头的头部。值得注意的是,只有PowerupC和MainC在其规范中分别真正地声明了使用接口Boot和提供接口Boot,才能保证这种连接不出现错误。
行(3)使用操作符“->”将组件PowerupC和LedsC通过接口Leds连接起来,PowerupC是接口Leds的使用者,LedsC是接口Leds的提供者。
只要保证箭头指向符合从接口使用者到接口提供者,操作符“->”和“<-”具有相同的连接效果。例如,下面两行是等价的。
1)快捷连接
在操作符“–>”的两侧是两个端点。通常情况下,端点由声明的组件实例及其接口实例组成,如配件LedsC中的行(4):LedsP.Led0->PlatformLedsC.Led0。操作符左侧的端点是LedsP.Led0,LedsP是模块LedsP的实例,Led0是LedsP规范中声明的类型为Leds的实例,名字为Led0。操作符右侧的端点是PlatformLedsC.Led0,由组件类型为PltaformLedsC的实例与接口类型Leds的实例组成。在连接语句中,要求两个端点的接口实例是同类型的。不能将不同类型的接口实例连接在一起,比如,不能将接口Boot连接到接口Leds上。对于参数化接口而言,接口类型及参数都应匹配,比如,不能将Read<unit8_t>与接口Read<int16_t>连接到一起。否则会产生编译错误。
在操作符两侧端点中组件的接口实例类型一致的前提下,如果某侧端点的接口实例不存在连接歧义性,那么可以将该端点的接口名字省略。这是一种快捷连接的写法。例如,在配件PowerupAppC中,行(2)与行(3)都使用了快捷连接写法。在操作符的左侧,端点MainC.Boot中使用的是接口Boot的一个实例。在操作符的右侧端点只写了组件名字PowerupC,没有接口Boot的名字。这是因为组件PowerupC仅提供了接口Boot的一个实例,nesC认为连接时就应该连接到这个实例。行(3)的情况类似。行(2)与行(3)是与下面两条语句等价的快捷连接写法。
类似的,这种快捷连接写法也可以应用到使用操作符“=”的输出连接语句中,如配件LedsC实现代码的行(2)和行(3)。LedsC的接口Leds和接口Init被连接到组件LedsP上。(www.xing528.com)
在输出接口的连接语句中,不能省去配件声明的输出接口的名字。例如在配件LedsC中的如下写法是错误的。
2)使用快捷连接时是接口类型而不是名字。
例3.19:
●组件BlinkC的规范
●组件TimnerMilliC的规范
下面的nesC连接语句是合法的。
TimerMilliC()是一个通用配件,生成实例的方法与其应用场景后面会详细介绍,这里只需将其看成一个普通组件即可。BlinkC.Timer0的类型是Timer<TMilli>,连接时搜索组件Timer0中类型为Timer<TMilli>的接口,发现有确定的类型匹配,因此能够正确连接。
如果连接中存在不确定性,即模糊性,那么就不能完成连接,会产生错误报告。例如,下面的连接语句是不合法的。由于组件BlinkC的规范声明了三个Timer接口实例,在连解语句中没有指出到底是哪个接口实例,存在不确定性,因此是无法实现连接的。
3)连接如何工作
为了更好地理解连接是如何工作,下面以接口Leds为例继续分析配件LedsC。配件LedsC将LedsC.Leds连接到LedsP.Leds上(行(3),后续会解释操作符“=”的作用)。组件PowerupAppC实现代码的行(2)和组件LedsC实现代码的行(3),将组件PowerupC中接口函数名字(局部变量)Leds.led0On与LedsP中的接口函数名字(局部变量)Leds.led0On匹配到一起。也就是说,在PowerupC组件中调用接口函数Leds.led0On(),实际上是调用了LedsP组件中的接口函数Leds.led0On()。阅读nesC编译器生成的部分中间代码(见apps/Powerup/build/相应平台下的app.c)有利于我们更好地理解这一点。另外,为了减少函数调用带来的开销,nesC编译器广泛使用内联函数(inline)来生成这些中间代码,最后编译成目标可执行代码时将这些函数的代码复制到被调用的地方。
(2)操作符“=”
除了顶层配件之外,其他的配件与模块相似,在其规范中也提供或使用接口。但是在配件实现中没有诸如模块那样的执行逻辑代码,也就是说,没有定义提供接口中命令或使用接口中事件的函数。因此,配件规范声明的接口必须由其他组件来定义。也就是说,用其他组件的接口为自己规范中声明的接口重命名。具体的实现方式是:使用关键字components声明一个组件;使用操作符“=”将其接口与自己规范中声明的接口连接起来。例如,在配件LedsC的规范中声明了提供的接口Leds。由于配件实现代码没有接口函数的定义,那么如何使得LedsC能够提供接口Leds呢?来看看其实现代码,先声明了组件LedsP(注意LedsP的规范中也提供接口Leds),再使用操作符“=”将自己提供的接口Leds与LedsP提供的接口Leds连接起来,也就是说,配件LedsC通过输出LedsP提供的接口Leds,以实现自己提供接口Leds。
站在编程者实现配件的角度来看,操作符“=”的作用是通过类似于重命名的方式,实现了配件规范中声明的接口。站在提供功能(或服务)的角度,操作符“=”的作用是将配件封装的某个组件的功能(或服务)输出。
(3)操作符“->”和“<-”与操作符“=”的比较
操作符“->”、“<-”和“=”都起到了连接的作用。只要操作符两侧的组件的接口类型一致,而且连接的组件的接口实例不存在模糊性,都可以使用快捷连接的写法。但是,在操作符的作用、连接对象以及语法细节方面仍然存在差异,具体见表3-2。
表3-2 操作符“->”/“<-”与“=”的对比
3.连接规则
在连接过程中,有两条规则需要遵循。
1)规则1:如果组件中使用关键字as为接口设置了别名,那么在配件实现代码的连接语句中必须使用这个别名。
例如,组件MainC与LedsP的规范,代码可参考例3.4和例3.5。
基于组件MainC与组件LedsP的规范,下面两行连接语句中第一行是合法的,第二行是不合法的。
2)规则2:配件在连接一个组件之前必须先声明它。
前面列举的例子中,配件的实现代码都是声明组件语句放在前面,连接语句放在后面。事实上,声明组件语句与连接语句之间可以有交叉,但是必须在连接语句之前声明该组件。
例3.20:
一种合法的配件PowerupAppC的写法如下:
相对的,一种错误的配件PowerupAppC的写法如下。这是因为在行(2)中连接语句使用了组件PowerupC,而在该连接语句之前没有声明PowerupC。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。