当前位置:Linux教程 - Linux文化 - OSKit的部件对象模型 第三章

OSKit的部件对象模型 第三章


第三章 OSKIT中COM机制的实现

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,则将释放整个对象的内存空间。