深入BREW抽象接口机制 作者:东方欲晓
抽象接口,接口,虚基类都是同一个概念。只不过是在不同的运用场合叫不同的名字而已。在c++中,我们称之为虚基类,在java中则称为接口,而在我们可爱的BREW中则芳名为“抽象接口”。既然只是不同的别名,本质是一致的,那么这个本质是什么那?本质就是在基类的层次上定义一个“空函数集”,通过该空函数集实现一个抽象的,虚拟的,通用的接口层,可以在运行时根据对象的实际类型动态地调用派生类的那些“具体”的函数,简言之,就是同一个接口函数,根据具体的对象,调用具体的函数。这种机制,在c++和java中是在语言级被支持的,通过编译器的滞后绑定来实现多态。 而在brew中,则是通过巧妙的设计来实现的(因为c语言在语言级上不支持面向对象)。
ok,闲话少说,现在我们就来看brew中的抽象接口吧。
brew中,抽象接口其实就是通过Qinterface或者AEEInterface定义了一个包含VTBL的接口,其他就什么都没有了。抽象接口没有相关的NEW函数,所以也就不可能被实例化(在BREW中,每个除抽象接口外的Interface或者Class都有相关的XXX_New函数,用来创建并实例化具体的对象,等价于C++中的类构造函数。但是BREW并不允许用户直接调用它来创建对象,而是采用了设计模式中的Factory创建模式来生成对象,采用单独的Interface,IShell来作为对象的工厂,由IShell来专门负责对象的创建,通过传入的ClsID,IShell会自动去调用相应的Class的New函数来创建对象,并返回实例指针)。同时,抽象接口也没有实例化的必要,XXX_New函数作了什么,无非是3件事情,为对象分配内存,实例化VTBL,初始化数据成员。而抽象接口那,没有数据成员,不需要实例化vtbl(注意,抽象接口只不过是定义了空函数集,即vtbl表,但是没有必要也不应该去实现它,具体实现是在派生的具体类中在创建时通过vtbl表的实例化来完成的)。请记住,抽象接口在BREW中只是提供了一套类似于全局函数的(我的意思是,运用抽象接口的接口函数是不需要先创建对象的,而是直接可以运用,但是传入的对象却是需要创建的)抽象函数集,而在运行时,根据具体传入的对象类型(虽然调用抽象接口函数时,传入的类型都是该抽象接口的指针类型,但实际上都已经通过了类型的强制转换,即类似于C++中的派生类向基类的强制转换,这种转换是允许的,允许的根本原因是因为派生类把基类作为对象地址空间的开始的第一项)调用了具体派生接口的“具体”函数。这是怎么做到的那,其实是通过那些几乎所有接口都会用到的宏。define IVirtualInterface_Fun(p,a,b) GetPVtbal(p,a,b)->Fun 这里p是IVirtualInterface指针类型。用户调用IVirtualInterface_Fun(p,a,b)时,其实是调用了对象vtbl表中的相应的函数。而当调用抽象接口函数时,虽然此时传入的是IVirtualInterface类型指针,但是其实质是派生的具体接口类型,所以通过GetPVtbal(p,a,b)->Fun调用到的就是属于它(派生具体接口)的具体接口函数了。 基本情况就介绍这么多了,下面我们来看BREW中几个比较典型的抽象接口,IAPPLET,ICONTROL,IIMAGE。
对于IAPPLET这个抽象接口实在太重要了,每个applet应用,其实归根结底是从这个接口派生出的,而不是AEEAPPLET。我们慢慢道来。IAPPLET只定义了VTBL表,其中最重要的函数就是Handle_Event。 AEEAPPLET派生于IAPPLET,增加了很多数据成员,比如ishell等等。当调用AEEAPPLET_New的时候,默认传入AEEAPPLET结构,并且传入用户指定的handleevent函数。此时,其实就是在实例化AEEAPPLET,同时实例化IAPPLET中的Handle_Event。这样,当每次brew event分发的时候,brew只会调用抽象接口IAPPLET的接口函数,IAPPLET_HandleEvent。但此时,其实就是调用了用户指定的派生类AEEAPPLET中的相应的已经实例化的HandleEvent了,这种机制前面已经谈到过,是由抽象接口的本质来实现的。 而在用户的HandleEvent(其实就是AEEAPPLET中vtbl中的handleevent)中,通常第一步代码,就是将传入的抽象接口IAPPLET的指针强制转换成AEEAPPLET,从而才能访问AEEAPPLET中的数据成员,操作这些数据成员(为了运用抽象接口机制,传递的参数都是抽象接口类型(相当于基类类型),但是在派生具体接口的函数中是需要访问自身结构空间的,所以需要转换成自身类型,由于抽象接口必定是派生接口的第一个成员,所以保证了这种转换的正确性)。另外,BREW中还允许在Applet中指定自己的结构类型,不过第一项一定要是AEEAPPLET,其实归根结底,这一限制就是为了让用户指定的结构的第一项是IAPPLET,即是派生于IAPPLET抽象接口的。这样才能在handleevent中实现IAPPLET到用户类型的转换。
对于IControl这个抽象接口,它的派生接口主要有Imenu,Itextctl,IStatic等等。它定义了控件通用的一些行为,但是并没有实现。需要指出的是,IControl的所有派生接口,均处于AEE层,即开发者都可以直接运用。那既然用户可以直接用这些派生类接口,还要IControl干吗那?的确,IControl接口对用户而言不是必须使用的(不像IApplet接口,brew无时无刻不在运用),但是运用IControl接口,可以使用户开发出一些灵活的运用,比如用统一的IControl接口函数来遍历所有的control等,在高通的Icontrol介绍文档中有详细说明。这里不在祥诉。
而对于IIMAGE接口,则和IControl接口有所不同,IImage接口的派生接口为JPEG接口,PNG接口等各种具体图像类型的接口,而这些具体的派生接口均位于OEM层,即对于开发者而言不可见,也就不能用了。这是因为,对于不同图像采用不同解码,显示机制对上层应该是屏蔽的,即,上层只需传入图片,就应该调用相同的函数来实现显示,具体的差异性应该是OEM层来处理的。在BREW中,当用户调用IShell_LoadImage并传入图片时,brew其实就是根据图片的类型,具体创建了OEM层的派生接口的实例,并且将派生接口的实例强制转换成IIMage抽象接口指针,以便后续IImage接口函数的调用。用户获得这个IImage接口指针后,就可以调用后续的相应IImage接口函数来实现显示了,而这些后续操作,由于这个IIMage指针的实际类型是派生类的类型,所以由抽象接口机制,最终也就调用了OEM的具体接口的函数了。这样的话,也就实现了,JPEG接口用来负责jpeg图片的显示,PNG接口负责PNG图片的显示。 而对于上层而言,却是根本不知道的,也没有必要知道。真是妙压!这种例子还有,IFont接口同样如此,为何IFont需要采用这种机制,读者现在应该很清楚了把。 而前面一种IControl的例子,IAStream也是一样,它的派生接口IFile,ISocket也同样位于AEE层,为何要位于AEE层,我想读者也清楚了把。
我想写的就是这些了,希望能对大家有所帮助,有什么错误之处,请指教。
抽象接口,接口,虚基类都是同一个概念。只不过是在不同的运用场合叫不同的名字而已。在c++中,我们称之为虚基类,在java中则称为接口,而在我们可爱的BREW中则芳名为“抽象接口”。既然只是不同的别名,本质是一致的,那么这个本质是什么那?本质就是在基类的层次上定义一个“空函数集”,通过该空函数集实现一个抽象的,虚拟的,通用的接口层,可以在运行时根据对象的实际类型动态地调用派生类的那些“具体”的函数,简言之,就是同一个接口函数,根据具体的对象,调用具体的函数。这种机制,在c++和java中是在语言级被支持的,通过编译器的滞后绑定来实现多态。 而在brew中,则是通过巧妙的设计来实现的(因为c语言在语言级上不支持面向对象)。
ok,闲话少说,现在我们就来看brew中的抽象接口吧。
brew中,抽象接口其实就是通过Qinterface或者AEEInterface定义了一个包含VTBL的接口,其他就什么都没有了。抽象接口没有相关的NEW函数,所以也就不可能被实例化(在BREW中,每个除抽象接口外的Interface或者Class都有相关的XXX_New函数,用来创建并实例化具体的对象,等价于C++中的类构造函数。但是BREW并不允许用户直接调用它来创建对象,而是采用了设计模式中的Factory创建模式来生成对象,采用单独的Interface,IShell来作为对象的工厂,由IShell来专门负责对象的创建,通过传入的ClsID,IShell会自动去调用相应的Class的New函数来创建对象,并返回实例指针)。同时,抽象接口也没有实例化的必要,XXX_New函数作了什么,无非是3件事情,为对象分配内存,实例化VTBL,初始化数据成员。而抽象接口那,没有数据成员,不需要实例化vtbl(注意,抽象接口只不过是定义了空函数集,即vtbl表,但是没有必要也不应该去实现它,具体实现是在派生的具体类中在创建时通过vtbl表的实例化来完成的)。请记住,抽象接口在BREW中只是提供了一套类似于全局函数的(我的意思是,运用抽象接口的接口函数是不需要先创建对象的,而是直接可以运用,但是传入的对象却是需要创建的)抽象函数集,而在运行时,根据具体传入的对象类型(虽然调用抽象接口函数时,传入的类型都是该抽象接口的指针类型,但实际上都已经通过了类型的强制转换,即类似于C++中的派生类向基类的强制转换,这种转换是允许的,允许的根本原因是因为派生类把基类作为对象地址空间的开始的第一项)调用了具体派生接口的“具体”函数。这是怎么做到的那,其实是通过那些几乎所有接口都会用到的宏。define IVirtualInterface_Fun(p,a,b) GetPVtbal(p,a,b)->Fun 这里p是IVirtualInterface指针类型。用户调用IVirtualInterface_Fun(p,a,b)时,其实是调用了对象vtbl表中的相应的函数。而当调用抽象接口函数时,虽然此时传入的是IVirtualInterface类型指针,但是其实质是派生的具体接口类型,所以通过GetPVtbal(p,a,b)->Fun调用到的就是属于它(派生具体接口)的具体接口函数了。 基本情况就介绍这么多了,下面我们来看BREW中几个比较典型的抽象接口,IAPPLET,ICONTROL,IIMAGE。
对于IAPPLET这个抽象接口实在太重要了,每个applet应用,其实归根结底是从这个接口派生出的,而不是AEEAPPLET。我们慢慢道来。IAPPLET只定义了VTBL表,其中最重要的函数就是Handle_Event。 AEEAPPLET派生于IAPPLET,增加了很多数据成员,比如ishell等等。当调用AEEAPPLET_New的时候,默认传入AEEAPPLET结构,并且传入用户指定的handleevent函数。此时,其实就是在实例化AEEAPPLET,同时实例化IAPPLET中的Handle_Event。这样,当每次brew event分发的时候,brew只会调用抽象接口IAPPLET的接口函数,IAPPLET_HandleEvent。但此时,其实就是调用了用户指定的派生类AEEAPPLET中的相应的已经实例化的HandleEvent了,这种机制前面已经谈到过,是由抽象接口的本质来实现的。 而在用户的HandleEvent(其实就是AEEAPPLET中vtbl中的handleevent)中,通常第一步代码,就是将传入的抽象接口IAPPLET的指针强制转换成AEEAPPLET,从而才能访问AEEAPPLET中的数据成员,操作这些数据成员(为了运用抽象接口机制,传递的参数都是抽象接口类型(相当于基类类型),但是在派生具体接口的函数中是需要访问自身结构空间的,所以需要转换成自身类型,由于抽象接口必定是派生接口的第一个成员,所以保证了这种转换的正确性)。另外,BREW中还允许在Applet中指定自己的结构类型,不过第一项一定要是AEEAPPLET,其实归根结底,这一限制就是为了让用户指定的结构的第一项是IAPPLET,即是派生于IAPPLET抽象接口的。这样才能在handleevent中实现IAPPLET到用户类型的转换。
对于IControl这个抽象接口,它的派生接口主要有Imenu,Itextctl,IStatic等等。它定义了控件通用的一些行为,但是并没有实现。需要指出的是,IControl的所有派生接口,均处于AEE层,即开发者都可以直接运用。那既然用户可以直接用这些派生类接口,还要IControl干吗那?的确,IControl接口对用户而言不是必须使用的(不像IApplet接口,brew无时无刻不在运用),但是运用IControl接口,可以使用户开发出一些灵活的运用,比如用统一的IControl接口函数来遍历所有的control等,在高通的Icontrol介绍文档中有详细说明。这里不在祥诉。
而对于IIMAGE接口,则和IControl接口有所不同,IImage接口的派生接口为JPEG接口,PNG接口等各种具体图像类型的接口,而这些具体的派生接口均位于OEM层,即对于开发者而言不可见,也就不能用了。这是因为,对于不同图像采用不同解码,显示机制对上层应该是屏蔽的,即,上层只需传入图片,就应该调用相同的函数来实现显示,具体的差异性应该是OEM层来处理的。在BREW中,当用户调用IShell_LoadImage并传入图片时,brew其实就是根据图片的类型,具体创建了OEM层的派生接口的实例,并且将派生接口的实例强制转换成IIMage抽象接口指针,以便后续IImage接口函数的调用。用户获得这个IImage接口指针后,就可以调用后续的相应IImage接口函数来实现显示了,而这些后续操作,由于这个IIMage指针的实际类型是派生类的类型,所以由抽象接口机制,最终也就调用了OEM的具体接口的函数了。这样的话,也就实现了,JPEG接口用来负责jpeg图片的显示,PNG接口负责PNG图片的显示。 而对于上层而言,却是根本不知道的,也没有必要知道。真是妙压!这种例子还有,IFont接口同样如此,为何IFont需要采用这种机制,读者现在应该很清楚了把。 而前面一种IControl的例子,IAStream也是一样,它的派生接口IFile,ISocket也同样位于AEE层,为何要位于AEE层,我想读者也清楚了把。
我想写的就是这些了,希望能对大家有所帮助,有什么错误之处,请指教。