3.1 OSKIT中COM的基本定义
在OSKIT中有关COM的基本定义在com.h中,在该文件中定义了OSKIT中的iid,COM的调用规范,以及OSKIT中的Iunknown接口。
OSKIT中GUID的定义如下:
struct oskit_guid {
oskit_u32_t data1;
oskit_u16_t data2;
oskit_u16_t data3;
oskit_u8_t data4[8];
};
其中oskit_u32_t、oskit_u16_t、oskit_u8_t分别是32位、16位、8位无符号整形。OSKIT使用这样的定义而不使用例如win32中的DWORD有两方面的原因:首先,这样定义使OSKIT保持一致的风格,向开发人员呈现出一致的命名规则。其次,如果oskit在win32或相似的环境中使用,能避免与实际的WIN32头文件冲突。
OSKIT中的调用规范如下:
#define OSKIT_COMCALL OSKIT_STDCALL
#define OSKIT_COMDECL oskit_error_t OSKIT_COMCALL
#define OSKIT_COMDECL_U oskit_u32_t OSKIT_COMCALL
#define OSKIT_COMDECL_V void OSKIT_COMCALL
其中,OSKIT_STDCALL是在compile.h中定义的有关编译器设置的宏,这个宏在OSKIT安装前的configure步骤中被赋值(configure步骤请见第一章)。OSKIT这样定义调用规范,是为了用不同的编译器都可以编译OSKIT,而不必在编写程序时考虑这些调用的细节。而OSKIT_COMDECL就是带错误返回码的调用,OSKIT_COMDECL_U是返回值为32位无符号整型的调用,OSKIT_COMDECL_V是不带返回值的调用。
OSKIT中Iunknown接口的定义如下:
struct oskit_iunknown
{
struct oskit_iunknown_ops *ops;
};
typedef struct oskit_iunknown oskit_iunknown_t;
struct oskit_iunknown_ops
{ / * 查询对象是否支持某个特定的接口,若支持则返回该接口的指针*/
OSKIT_COMDECL (*query)(oskit_iunknown_t *obj,
const oskit_iid_t *iid, void **out_ihandle);
/* 增加某个接口的引用计数*/
OSKIT_COMDECL_U (*addref)(oskit_iunknown_t *obj);
/* 将对某个接口或对象的引用计数减1,若引用计数为0,则释放该接口或对象 */
OSKIT_COMDECL_U (*release)(oskit_iunknown_t *obj);
};
其中,query的参数中obj是要查询的COM对象,iid是要查询的接口的全局标识符,若查询成功,out_ihandle返回指向被查询接口的指针,若查询失败,则返回空指针。
上面可以看到,oskit_iunknown结构中存放着指向函数表格的指针,称为ops,而函数表格是通过一个称为oskit_iunknown_ops的结构实现的,这个结构中定义了指向该接口所实现的所有成员函数的指针。只要对该结构进行赋值,就可以通过这个结构调用该接口的所有成员函数。
由于Iunknown接口是由各个COM对象自己实现的,因此在COM.H中没有对该结构进行赋值,后面章节中将会详细剖析OSKIT中一个COM对象的实例,那时再对该结构的赋值进行讨论。
COM.H还通过宏定义对Iunknown接口的成员函数进行了包装,如下所示:
#define oskit_iunknown_query(obj, iid, out_ihandle) \
((obj)->ops->query((oskit_iunknown_t *)(obj), (iid),
(out_ihandle)))
#define oskit_iunknown_addref(obj) \
((obj)->ops->addref((oskit_iunknown_t *)(obj)))
#define oskit_iunknown_release(obj) \
((obj)->ops->release((oskit_iunknown_t *)(obj)))
这使开发人员可以对它们的调用更加方便。此外,顺便提一句,OSKIT的一大特色就是对宏定义的灵活使用。在这方面值得大家借鉴。
3.2 实例分析Iunknown接口的实现
OSKIT中并不是每个接口都允许被引用多次,比如内存接口,当系统初始化时便对定义好的静态内存结构进行赋值,然后所有对内存的操作都依赖于该内存接口,也就是说,在该内存接口中不存在实现Iunknown接口的问题。但也有可以存在可以引用多次的接口,如流接口(stream),C环境接口(OSKIT为核心提供了一个最小的C库,开发这也可以使用自己定义的C库加以替换)。本节就以C环境接口为例分析OSKIT中Iunknown接口的实现。
3.2.1 C环境对象的结构
OSKIT中C环境对象的结构如下:
struct genv {
oskit_libcenv_t libcenvi; /* C环境接口 */
int count; /* 引用计数 */
oskit_fsnamespace_t *fsn;
char hostname[256];
oskit_ttystream_t *console;
void (*exit)(int);
void (*siginit)(int (*func)(int, int, void *));
#ifndef PTHREADS
oskit_timer_t *sleep_timer;
oskit_osenv_sleep_t *sleep_iface;
osenv_sleeprec_t *sleeprec;
#endif
};
3.2.2 C环境接口及其对Iunknown接口的实现
C环境接口是在libcenv.h中定义的,为了让大家对OSKIT中COM接口有一个完整的认识,现将整个接口的说明列出:
struct oskit_libcenv {
struct oskit_libcenv_ops *ops; /* 指向函数表格的指针 */
};
typedef struct oskit_libcenv oskit_libcenv_t;
下面是函数表格:
struct oskit_libcenv_ops {
/* 对Iunknown接口的继承 */
OSKIT_COMDECL_IUNKNOWN(oskit_libcenv_t)
/* 获得及设置根文件系统 */
OSKIT_COMDECL (*getfsnamespace)(oskit_libcenv_t *s,
oskit_fsnamespace_t **out_dir);
OSKIT_COMDECL (*setfsnamespace)(oskit_libcenv_t *s,
oskit_fsnamespace_t *dir);
/* 设置及获得主机名 */
OSKIT_COMDECL (*gethostname)(oskit_libcenv_t *s,
char *hostname, int len);
OSKIT_COMDECL (*sethostname)(oskit_libcenv_t *s,
const char *hostname, int len);
/* 调用及设置退出函数 */
OSKIT_COMDECL_V (*exit)(oskit_libcenv_t *s,
oskit_u32_t exitval);
OSKIT_COMDECL (*setexit)(oskit_libcenv_t *s,
void (*exitfunc)(int));
/* 设置及获得终端对象 */
OSKIT_COMDECL (*getconsole)(oskit_libcenv_t *s,
oskit_ttystream_t **out_ttystream);
OSKIT_COMDECL (*setconsole)(oskit_libcenv_t *s,
oskit_ttystream_t *ttystream);
/* 初始化信号库 */
OSKIT_COMDECL (*signals_init)(oskit_libcenv_t *s,
int (*func)(int, int, void *));
OSKIT_COMDECL (*setsiginit)(oskit_libcenv_t *s,
void (*sigfunc)(int (*func)(int,int,void *)));
/* 睡眠/唤醒接口 */
OSKIT_COMDECL_V (*sleep_init)(oskit_libcenv_t *s,
osenv_sleeprec_t *sleeprec);
OSKIT_COMDECL_U (*sleep)(oskit_libcenv_t *s,
osenv_sleeprec_t *sleeprec,
struct oskit_timespec *timeout);
OSKIT_COMDECL_V (*wakeup)(oskit_libcenv_t *s,
osenv_sleeprec_t *sleeprec);
/* 克隆整个对象 */
OSKIT_COMDECL (*clone)(oskit_libcenv_t *s,
oskit_libcenv_t **intf);
};
下面是定义C环境接口的iid
extern const struct oskit_guid oskit_libcenv_iid;
#define OSKIT_LIBCENV_IID OSKIT_GUID(0x4aa7dfe9, 0x7c74, 0x11cf, 0xb5, 0x00, 0x08, 0x00, 0x09, 0x53, 0xad, 0xc2)
下面是对C库接口成员函数的包装,因为长度缘故,只写出部分:
#define oskit_libcenv_query(s, iid, out_ihandle) \
((s)->ops->query((oskit_libcenv_t *)(s), (iid), (out_ihandle)))
#define oskit_libcenv_addref(s) \
((s)->ops->addref((oskit_libcenv_t *)(s)))
#define oskit_libcenv_release(s) \
((s)->ops->release((oskit_libcenv_t *)(s)))
下面是C库接口的实现:
static struct genv default_libcenv;
/* 定义静态的C环境对象 */
在OSKIT中,经常在初始化时大量使用静态定义,因为这样定义出来的结构在编译时就分配好了空间,使系统在启动时就可以使用这些结构,而不需要等到内存对象初始化完之后再动态分配空间。
Query 成员函数的流程图见下一页:
下面是Query成员函数的实现:
static OSKIT_COMDECL
libcenv_query(oskit_libcenv_t *s, const oskit_iid_t *iid,
void **out_ihandle)
{
struct genv *g = (struct genv *) s;
/* 将指向C库环境接口的指针转化为指向C库环境对象的指 */
if (memcmp(iid, &oskit_iunknown_iid, sizeof(*iid)) == 0 ||
memcmp(iid, &oskit_libcenv_iid, sizeof(*iid)) == 0)
/* 通过比较要查询的iid和iunknown接口的iid以及c库环境接口的iid
相比较,如果相等,则表明该C库环境对象支持该接口 */
{ *out_ihandle = &g->libcenvi; /*返回指向C环境接口的指针*/
g->count++; /*将引用计数加1*/
return 0;
}
*out_ihandle = 0;
return OSKIT_E_NOINTERFACE; /* 如果找不到,则返回出错信息 */
};
/* 增加引用计数 */
static OSKIT_COMDECL_U libcenv_addref(oskit_libcenv_t *s)
{ /* 将C库环境的接口指针转化为C库环境对象指针 */
struct genv *g = (struct genv *) s;
assert(g->count);
return ++g->count; /*增加引用计数*/
}
Release 函数的流程见下一页:
/* 释放引用计数 */
libcenv_release(oskit_libcenv_t *s)
{ /* 将C库环境接口指针转化为C库环境对象指针 */
struct genv g = (struct genv ) s;
assert(g->count);
/* 以下语句先将对象的引用计数减1,然后判断计数是否为0,若是则释
放对与其相关的接口的引用*/
if (--g->count == 0) {
if (g->fsn) oskit_fsnamespace_release(g->fsn);
if (g->console) oskit_ttystream_release(g->console);
#ifndef PTHREADS
if (g->sleep_timer)
oskit_timer_release(g->sleep_timer);
if (g->sleep_iface)
oskit_osenv_sleep_release(g->sleep_iface);
#endif
sfree(g, sizeof(g));
}
return g->count; /*返回该对象的引用计数*/
}
现在对上面Iunknown接口的三个成员函数的实现思路作一定的解释。在OSKIT中,客户所能使用的只是对象提供给用户的接口指针,当用户在该接口中调用Query方法时,例如libcenv_query,并对该对象中的某个接口进行查询,这时,由于对象是否实现所查询接口是在对象结构中定义的,因此首先把接口指针进行强制类型转换,变为指向接口所属对象的指针,然后再将所要查询的接口iid与对象所实现的所有接口的iid逐一比较,如果与其中某一个接口的iid相等,则返回指向该接口的指针,如果无一相等,则返回错误信息。当用户使用addref方法时,同样也是先进行类型转换,然后将这个对象中的引用计数加1,若用户调用release方法,在进行类型转换后,将对象的引用计数减1,如果发现引用计数位0,则将释放整个对象的内存空间。