玩UEFI的人,应该知道UEFI的核心是用C语言来描述的,C语言是一个面向过程的语言,它不具备C++/Java的面向对象的特性。但是仔细品味code我们就能感觉得到UEFI虽然是用C语言来描述的,但是里面大量的用到了面向对象的思想,在类似于protocol,ppi的架构中,就能很容易找到用C语言来实现面向对象特性的痕迹,下面来简单总结下这个方面的东西。C语言在组织功能块源代码方面有很多种方案,大概我们总结下我见过的几种模式:
1.使用文件作为软件功能模块的封装边界;实现方式大概就是公共变量和外部的方法在头文件里可见,而在对应的实现文件当中包含方法的实现,私有变量和私有方法,在这种方案里面,我们看在功能块的实现文件里面去实现我们需要的功能,在其他的模块中如果需要调用这个模块提供的服务,我们就可以在其他的模块当中去使用#include来包含模块的头文件,例如:
<module.h>
#ifndef _Module_H_
#define _Module_H_
#define FALSE (0)
#define TURE (~ FALSE )
#typedef
extern unsigned int module g_data; //公共数据
extern int module_g_func(char data);//公共方法
#endif
<module.c>
#include"module.h"
#include <stdio.h>
unsigned int module_g_data;
int module_g_func (char data){
printf("Cstyle的C语言笔记,代码功能块封装模式:实例1");
return TURE;
}
2.这种方案里面使用C语言当中非常重要的的一个工具结构体来封装数据,在头文件当中我们先定义一个结构来把的我们关注的数据放在一个结构图里面,然后申明对数据进行操作的方法,最后在实现文件当中来实现这些方法,当然我们需要给方法传递一个参数,这里一般是传递一个指向数据的指针。在这种设计情况下,我们可以有多个相同构造的对象(DATA_TYPE),但是分配不同的内存空间(使用mollac等服务),同样的方法可以作用在这一组对象上,我们还可以为其提供构造方法,析构方法,获取对象方法,设置对象方法等,可以说这种设计已经能基本满足我们大部分的引用需求了。在我们的UEFI当中有非常多的应用。
<module.h>
#ifndef _Module_H_
#define _Module_H_
#define FALSE (0)
#define TURE (~ FALSE )
#typedef
typedef struct _DATA_TYPE DATA_TYPE ;
struct _DATA_TYPE{
char * string;
int data;
float f_data;
}
int module_function(DATA_TYPE *this);
#endif
<module.c>
#include "modle.h"
#include "stdio.h"
int module_function(DATA_TYLE *this){
printf("Cstyle的C语言笔记,代码功能块封装模式:实例2");
return TURE;
}
3.第三种方案是在第二种方案的基础上,进一步进行封装,用来模拟面向对象的特性,这种在UEFI当中的protocol/PPI的实现里面很常见。
<module.h>
#ifndef _Module_H_
#define _Module_H_
#define FALSE (0)
#define TURE (~ FALSE )
typedef struct _DATA_TYPE DATA_TYPE ;
typedef int(*MODULE_FUNCTION)(DATA_TYPE *this);
struct _DATA_TYPE{
char * string;
int data;
float f_data;
MODULE_FUNCTION module_function;//函数指针
}
#endif
<module.c>
#include "modle.h"
#include "stdio.h"
int module_function(DATA_TYLE *this);
DATA_TYPE data={
"abc123",
1234,
1.2345,
module_function
};
int module_function(DATA_TYLE *this){
printf("Cstyle的C语言笔记,代码功能块封装模式:实例3");
return TURE;
}
上面的几种组织模式都仅限于单个的功能模块的代码组织模式,它优点在于可以把相关的功能块放在一起封装起来,组成一个黑盒子对外提供服务,很好的解决了系统当中的资源组织架构混乱的问题。但是它也有自己的缺点,那就是模块的可重用性和扩展性能较差,个人建议从以下几个方面进行改进:
a.对于非常简单的系统,我们可以使用第1种方案,它的优点在于实现非常简单,但需要注意的地方在于变量的命名规则,虽然来说每个公司都有其特有的命名规则,但是个人建议使用module_g_name/module_g_func()这一类标示出变量/函数的作用域。
b.对于我们需要处理的对象的数据有比较明显的结构的,比如数据包这类,他会有固有的数据格式,这时候使用第2种方案的话,能够非常容易的处理这类问题。再例如你要处理一组的LED灯的亮灭,我们就可以使用结构体,共同体,位域来很好的把数据封装起来,然后用其提供的方法来操作数据。
c.第3种的最大的好处是它既封装了数据与操作数据的方法,又很好的把的定义和实现分离开来;这一点最像面向对象里面的类和对象的关系。但是它仅仅是单例的,并不能实现方法和数据的扩展和继承。在它的基础上我们可以做出更多的灵活的高级功能来。下面举例说明:
c1.我们可以在方法3当中的结构图里面添加两个成员,一个是void类型的函数指针p,一个是标签T用来做判断条件;这样当我们需要改变类(暂且这么称呼)的方法的是,我们就可以根据T来判断,动态给p赋值来实现类似于多态的特性。
c2.我们可以利用在方法3当中做好的功能,来扩充其功能同时保证原来的功能不变,这一点就类似于面向对象中的继承。要实现这一点我们就可以在新的模块当中include当前的模块的头文件,同时在定义新的类的时候,把当前的类当中成员变量包含进去,类似下面的做法。
<module2.h>
#ifndef _Module2_H_
#define _Module2_H_
#include “module.h”
#define FALSE (0)
#define TURE (~ FALSE )
typedef struct _DATA_TYPE2 DATA_TYPE2 ;
typedef int(*MODULE_FUNCTION)(DATA_TYPE2 *this);
struct _DATA_TYPE2{
DATA_TYPE2 *data;//保留原来的功能
char * string2;//增加新的数据项
MODULE_FUNCTION module_function;//函数指针,重构函数功能
}
#endif
关于C语言当中组织代码结构,让其有类似于C++当中的面向对象的特性的各种方法就先说到这里。