[NAME]
ALL.daovm.interface.extending

[TITLE]
扩展道虚拟机

[DESCRIPTION]

帮助条文daovm.interface.embedding里的演示表明道虚拟机的嵌入方式极其简单。 这里将演示道虚拟
机的扩展方式也极其简单。 因为道语言支持函数参数的类型标注,它可以省去你写很多样板代码和参数
类型检查代码的麻烦。 也就是说,给道语言封装C/C++函数要比给其他如Python和Lua之类的语言封装C/
C++函数要简单很多。

 1   第一个扩展模块例子     
 1   第一个扩展模块例子     
 1   第一个扩展模块例子     


 1.1   扩展函数原型  

所有可被道调用的函数都必须使用类似下面的原型: 
     
   1  void MyCFunction( DaoProcess *proc, DaoValue *param[], int nparam )
   2  {
   3      printf( "Hello Dao!\n" );
   4  }
     


 1.2   模块入口函数  

每个道模块都必须提供一个入口函数。下面这个"DaoOnLoad()"是个基本的入口函数名, 后面将会介绍入
口函数名也可以包括模块名。 
     
   1  // Entry function for each C/C++ module:
   2  int DaoOnLoad( DaoVmSpace *vmspace, DaoNamespace *ns );
     
这个函数将在模块载入时被调用,以便模块注册它的函数和类型等。 此函数的第一个参数是一个虚拟机
空间DaoVmSpace对象。 此对象负责模块的载入和管理。 第二个参数是自动生成的命名空间对象,此对象
将代表此模块,因此这个模块的 函数和类型等都应该注册到此命名空间。

如前面提到的,模块入口函数的名称可以包括模块的名称。 假如模块的名字以load abcload path.
abc 的方式出现在模块载入语句中,那么该名称可以如下方式出现在入口函数名中 (并且搜索方式也如
下): 
  1. 全小写字母: 例如 Daoabc_OnLoad;
  2. 首大写字母: 例如 DaoAbc_OnLoad;
  3. 全大写字母: 例如 DaoABC_OnLoad; 


 1.3   函数注册  

下面的函数可用来将一个函数注册到命名空间DaoNamespace对象: 
     
   1  // 注册单个函数:
   2  DaoRoutine*
   3  DaoNamespace_WrapFunction( DaoNamespace *self, DaoCFunction fp, const char *proto );
   4  
   5  // 注册多个函数:
   6  int DaoNamespace_WrapFunctions( DaoNamespace *self, DaoFuncItem *items );
     
我们将在这里仅介绍第一个函数,第二个函数将在后面的小节里介绍。 这两个函数的第一个参数都是命
名空间DaoNamespace对象, 函数将被注册到此对象里。第一个函数的第二个参数是函数指针类型
DaoCFunction, 它的原型跟我们前面准备的函数MyCFunction一致; 第三个参数则是被注册函数在那个
命名空间里的名称和原型。

下面将把上面的函数MyCFunction以"HelloDao()"注册到命名空间"nspace"里: 
     
   1  // 注册函数:
   2  DaoNamespace_WrapFunction( nspace, MyCFunction, "HelloDao()" );
     
那么这个函数将在道里可以一HelloDao不带任何参数调用。

 1.4   总结  

把上面的放在一起将够成一个最简单的道扩展模块: 
     
   1  #include "dao.h"
   2  #include "stdio.h"
   3  static void MyCFunction( DaoProcess *proc, DaoValue *param[], int nparam )
   4  {
   5      printf( "Hello Dao!\n" );
   6  }
   7  int DaoOnLoad( DaoVmSpace *vmspace, DaoNamespace *nspace )
   8  {
   9      DaoNamespace_WrapFunction( nspace, MyCFunction, "HelloDao()" );
  10      return 0;
  11  }
     
要编译这些代码,你将需要把道的头文件路径加到你的编译选项里。 并且你还需要声明以下预处理器定
义: 
  *  Win32 上: WIN32;
  *  Unix 上: UNIX;
  *  MacOSX 上: MACOSX; 
在Windows上链接时,你还需要将道库链接到你的模块里。 在其他平台上加如下链接标识就可以了: For
linking, on Windows you will need to link the module against the Dao library. But on the 
other platforms, you can simply use the following flags, 
  *  Unix 上: -rdynamic;
  *  MacOSX 上: -undefined dynamic_lookup; 
如果你使用DaoMake (tool.standard.daomake) 编译你的模块, 这些都将被自动处理好。

 2   第二个函数封装例子     
 2   第二个函数封装例子     
 2   第二个函数封装例子     

现在我们将展示如何封装可接受参数和可返回值的函数。 假定我们需要封装以下C函数 
     
   1  float Test( int id, const char *name, int extra );
     
并给最后的"extra"参数支持一个缺省值。 那么我们将需要封装的函数以下面的原型注册到道里: 
     
   1  # 道函数原型
   2  MyTest( id: int, name: string, extra = 0 ) => float
     
这样,此函数将可以接受一个整数和一个字符串作为参数,以及另一个整数作为额外的参数。 这个函数原
型也表示此函数将返回一个单精度浮点数。

在C封装函数里,将道数据类型转换到C数据类型的方式很简单,并且把C数据返回给道 的方式也很简单:
     
   1  void MyTestInC( DaoProcess *proc, DaoValue *param[], int nparam )
   2  {
   3      daoint id = param[0]->xInteger.value;
   4      char *name = param[1]->xString.value->chars;
   5      daoint extra = param[2]->xInteger.value;
   6      DaoProcess_PutFloat( proc, Test( id, name, extra ) );
   7  }
     
正如你所看到的,你不需要检查参数个数或类型,就可以直接得到参数的数据。 当道程序调用这个函数时
,它保证将有正确类型的参数值传递给这个函数。

但为了使用上述数据转换方式,你将需要在你的模块源文件里包含其他道虚拟机的头文件 (如daoValue
.h)。 并且你还需要对道语言的内部标准数据结构有所了解。 如果你只想使用单一的dao.h头文件,你将
需要使用那些 DaoValue_TryGetXXX()函数。这些函数将以很小的额外开销 作最少的类型检查,并返回适
当的C数据。 
     
   1  void MyTestInC( DaoProcess *proc, DaoValue *param[], int nparam )
   2  {
   3      daoint id = DaoValue_TryGetInteger( param[0] );
   4      char *name = DaoValue_TryGetChars( param[1] );
   5      daoint extra = DaoValue_TryGetInteger( param[2] );
   6      DaoProcess_PutFloat( proc, Test( id, name, extra ) );
   7  }
     
如果你是以合适的道函数原型注册了这个函数,那些DaoValue_TryGetXXX() 里的类型检查都将成功,获
得的都是正确的数据。 函数DaoProcess_PutFloat() 是用来从C封装函数里返回一个单精度浮点数 给道
程序。这个浮点数将被放在到虚拟进程上合适的地方。 详情请看以下小节。

现在这个函数可被注册为: 
     
   1  // 注册一个带参数和返回值的函数:
   2  DaoNamespace_WrapFunction( nspace, MyTestInC, "MyTest(id:int,name:string,extra=0)=>float" );
     


 3   C/C++类型的基本封装     
 3   C/C++类型的基本封装     
 3   C/C++类型的基本封装     

有两种使用C/C++类型来扩展道的方式。一种是不透明类型封装, 这种封装以不透明指针的方式访问被封
装的C/C++数据结构或对象。 这是封装已有的C/C++类型的标准方式。 另一种是定义用户定制的C类型,这
种类型实际是以道虚拟机可以访问的 方式定义C数据结构。

对于非透明的封装,道虚拟机内部数据结构DaoCdata 将被用来表示被封装的C/C++数据。而对每个用户定
制的C类型, 则需要定义对应的数据结构。因此定义用户定制的C类型可能会稍微 复杂一点。不过这两种
扩展类型在道虚拟机里的注册方式基本一致。 这里我将先介绍不透明的C/C++类型封装。

 3.1   类型信息结构  

C/C++扩展类型只有在道语言的命名空间注册后才可被使用。 要注册扩展类型,必须先定义一个类型信息
结构DaoTypeBase, 这样它才能以下面的方式注册: 
     
   1  DaoType* DaoNamespace_WrapType( DaoNamespace *self, DaoTypeBase *typer, int opaque );
   2  int DaoNamespace_WrapTypes( DaoNamespace *self, DaoTypeBase *typer[] );
     
这里opaque参数必须是0或者1:0表示透明封装(即用户定制)类型; 1则表示非透明封装。

 3.1.1  类型信息结构的定义 

下面是类型信息结构DaoTypeBase的定义: 
     
   1  /* 用于创建C/C++扩展类型的类型信息结构: */
   2  struct DaoTypeBase
   3  {
   4      const char    *name;      /* 类型名; */
   5      DaoTypeCore   *core;      /* 类型内部数据; */
   6      DaoNumItem    *numItems;  /* 成员常数列表(空元素结束); */
   7      DaoFuncItem   *funcItems; /* 成员方法类表(空元素结束); */
   8  
   9      /* 用于实现扩展类型的继承关系的类型信息结构数组: */
  10      DaoTypeBase   *supers[ DAO_MAX_CDATA_SUPER ];
  11  
  12      /* 用于将被封装的C/C++对象在派生类型和基类型做转换的函数指针数组: */
  13      FuncPtrCast    casts[ DAO_MAX_CDATA_SUPER ];
  14  
  15      /* 用来释放C/C++数据对象的内存的函数: */
  16      void  (*Delete)( void *self );
  17  
  18      /* 用来获取可垃圾回收的成员对象的函数: */
  19      void  (*GetGCFields)( void *self, DList *values, DList *lists, DList *maps, int remove );
  20  };
     
这个结构可用来定义创建道语言的扩展类型所需的信息。 显然每个扩展类型都需要一个名称,此名称可
在该结构的一个成员里设定。 该结构的第二个成员则是为内部使用所预留。它的其他成员将在接下来的
小结里作介绍。

 3.1.2  成员常数和方法 

DaoTypeBase结构的第三个成员numItems可用来声明 扩展类型的一组常数成员。常数的声明使用了以下
结构: 
     
   1  struct DaoNumItem
   2  {
   3      const char *name;   /* 常数名; */
   4      int         type;   /* 常数类型; */
   5      double      value;  /* 常数值; */
   6  };
     
这里常数类型必须是DAO_INTEGER, DAO_FLOATDAO_DOUBLE 之一。 这个数组必须以一个带空名称成员
的结构结束。 例如: 
     
   1  static DaoNumItem myTypeNumbers[] =
   2  {
   3      { "MODE_ONE", DAO_INTEGER, MODE_ONE },
   4      { "MODE_TWO", DAO_INTEGER, MODE_TWO },
   5      { NULL, 0, 0 }
   6  };
     
如果括在类型没有成员常量,成员numItems则可为空指针。

DaoTypeBase结构的第四个成员funcItems 则可用来声明扩展类型的构造方法和成员方法。 方法的声明
需使用以下结构: 
     
   1  struct DaoFuncItem
   2  {
   3      DaoCFunction  fpter;  /* 方法的C函数指针; */
   4      const char   *proto;  /* 方法的原型,如: name( parlist ) => return_type */
   5  };
     
这里面的成员fpterproto 必须跟以下函数参数里的一致: 
     
   1  DaoRoutine*
   2  DaoNamespace_WrapFunction( DaoNamespace *self, DaoCFunction fp, const char *proto );
     


 3.1.3  基类和类型转换函数 

DaoTypeBase结构的第三个成员supers可以用来 定义扩展类型间的继承关系。定义这种继承关系只需在
supers数组里 设定基类的类型信息结构,并以空指针结束该数组。

如果被封装的的类型是带虚函数或虚基类的C++类型,道语言将需要知道如何正确地 在派生类型和基类
间做转换/映射。 这可通过在成员数组casts里设定能做正确的 映射或转换的函数。如果该C++类型有虚
函数而没有虚基类,这些函数一般应该按以下方式实现:

     
   1  void* cast_Sub_Base( void *data, int down_casting )
   2  {
   3      if( down_casting ) return static_cast<Sub*>( (Base*)data );
   4      return dynamic_cast<Base*>( (Sub*)data );
   5  }
     
而如果该C++类型有虚基类,则这些函数一般应该按以下方式实现: 
     
   1  void* cast_Sub_Base( void *data, int down_casting )
   2  {
   3      if( down_casting ) return dynamic_cast<Sub*>( (Base*)data );
   4      return dynamic_cast<Base*>( (Sub*)data );
   5  }
     


 3.1.4  内存释放和垃圾回收 

如果被封装的C/C++类型的数据不能直接由C标准函数free()释放, 那么DaoTypeBase结构的Delete成员
函数指针必须 指向一个用户定义的适用于该类型的内存释放函数。 对于非透明封装类型,该类型数据的
非透明指针将被作为self参数传递给该函数; 而对于用户定制的扩展类型,该类型的整个数据结构指针
将被传递给该释放函数。

如果一个扩展类型包含了使用道引用计数的成员数据,并且可能形成循环引用, 那么成员函数指针
GetGCFields也需要指向一个恰当定义的函数。 这个函数的主要目的是将扩展类型里可导致循环引用的
成员暴露个道的垃圾回收器。 这函数的基本原型如下: 
     
   1  void  GetGCFields( void *self, DList *values, DList *lists, DList *maps, int remove );
     
在这个函数里,被该扩展类型直接引用的成员因该被推入到values列表; 而那些元素带引用计数的
DList成员则应推入到lists列表里; 那些元素带引用计数的DMap成员则应推入到maps列表里。

这个函数可以在垃圾回收的不同阶段,用不同的remove参数调用。 在扫瞄候选垃圾对象以确定真实的垃
圾对象的阶段调用该函数时,remove将为零。 而在垃圾对象的内存释放阶段,remove参数将为一, 这种情
况下,该函数必须把被推入values的成员指针置空。 因为推入values里的成员对象将被垃圾回收器直接
释放, 将那些成员重设空指针可防止可能的双重释放。

 3.2  一个简单的例子 

给定如下C++类: 
     
   1  class ClassOne
   2  {
   3      public:
   4      int  value;
   5  
   6      enum { CLASSONE_AA, CLASSONE_BB };
   7  
   8      ClassOne( int v );
   9  
  10      int Method( const char *s );
  11  };
     
它可按如下方式封装: 
     
   1  // 先声明封装函数:
   2  static void dao_ClassOne_ClassOne( DaoProcess *proc, DaoValue *p[], int n );
   3  static void dao_ClassOne_Method( DaoProcess *proc, DaoValue *p[], int n );
   4  // 声明常数成员以封装其成员枚举值:
   5  static DaoNumItem ClassOneNumbers[] =
   6  {
   7      { "CLASSONE_AA", DAO_INTEGER, CLASSONE_AA },
   8      { "CLASSONE_BB", DAO_INTEGER, CLASSONE_BB },
   9      { NULL, 0, 0 }
  10  };
  11  // 声明构造方法和成员方法:
  12  static DaoFuncItem ClassOneMethods[] =
  13  {
  14      // 跟类型名同名的方法将作构造方法处理:
  15      { dao_ClassOne_ClassOne,  "ClassOne( v: int )" },
  16      { dao_ClassOne_Method,    "Method( self: ClassOne, s: string ) => int" },
  17      { NULL, NULL }
  18  };
  19  static void ClassOne_Delete( void *self )
  20  {
  21      delete (ClassOne*) self;
  22  }
  23  // ClassOne的类型信息结构:
  24  static DaoTypeBase ClassOne_Typer = 
  25  {
  26      "ClassOne", NULL, ClassOneNumbers, ClassOneMethods,
  27      {NULL}, {NULL}, ClassOne_Delete, NULL
  28  };
  29  // ClassOne的类型结构:
  30  DaoType *dao_type_ClassOne = NULL;
  31  
  32  static void dao_ClassOne_ClassOne( DaoProcess *proc, DaoValue *p[], int n )
  33  {
  34      // 获得整型参数;
  35      daoint v = DaoValue_TryGetInteger( p[0] );
  36      // 创建ClassOne实例:
  37      ClassOne *self = new ClassOne( v );
  38      // 返回用dao_type_ClassOne类型封装的实例:
  39      DaoProcess_PutCdata( proc, self, dao_type_ClassOne );
  40  }
  41  static void dao_ClassOne_Method( DaoProcess *proc, DaoValue *p[], int n )
  42  {
  43      // 获得类型实例:
  44      ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne );
  45      // 获得字符串参数:
  46      char *s = DaoValue_TryGetChars( p[1] );
  47      int res = self->Method( s );
  48      // 返回整数结果:
  49      DaoProcess_PutInteger( proc, res );
  50  }
  51  int DaoOnLoad( DaoVmSpace *vmSpace, DaoNamespace *nspace )
  52  {
  53      // 将ClassOne注册为非透明封装类型:
  54      dao_type_ClassOne = DaoNamespace_WrapType( nspace, & ClassOne_Typer, 1 );
  55      return 0;
  56  }
     


因为ClassOne的成员value为公共访问成员, ClassOne的完整封装类型应当包含属性方法以支持对该成
员的直接访问。 这种属性方法的定义即为对成员访问的操作符.field.field=的重载。 例如,对于成员
value,下面的方法将使得它可以象公共成员那样使用。 
     
   1  // the getter and setter:
   2      { dao_ClassOne_GETF_value,  ".value( self: ClassOne ) => int" },
   3      { dao_ClassOne_SETF_value,  ".value=( self: ClassOne, value: int )" },
     
这里函数名称dao_ClassOne_GETF_valuedao_ClassOne_SETF_value 可以随便设定。它们的基本实现如
下: 
     
   1  static void dao_ClassOne_GETF_value( DaoProcess *proc, DaoValue *p[], int n )
   2  {
   3      ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne );
   4      DaoProcess_PutInteger( proc, self->value );
   5  }
   6  static void dao_ClassOne_SETF_value( DaoProcess *proc, DaoValue *p[], int n )
   7  {
   8      ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne );
   9      self->value = DaoValue_TryGetInteger( p[1] );
  10  }
     


 3.3  一个更高级的例子 

在这个更高级的例子里我们将看到对继承和虚函数的封装处理。 这里我们有一个从ClassOne派生的类:
     
   1  class ClassTwo : public ClassOne
   2  {
   3      public:
   4      virtual void VirtualMethod( int i, float f );
   5  };
     
因为这个类含有虚函数,如果我们需要支持从此类的封装类型派生的道语言类可以 重新实现这个虚函数
,那么这个类的封装将会复杂些。

首先我们需要定义一个代理类,这个代理类将从我们要封装的类即ClassTwo 派生,并要重新实现它的虚
函数。这个重新实现的虚函数将负责检查调用此虚函数的类实例 是否源自于该封装类型的道派生类型,
且是否重新实现了该虚函数。 若是,这个代理类的虚函数将调用道派生类所重新实现的虚函数, 否则它
将调用基类ClassTwo的虚函数。 当这个封装类型的实例被创建时,实际被创建的C++类实例将是该类的代
理类的实例。

下面是一个例子代理类: 
     
   1  class Dao_ClassTwo : public ClassTwo
   2  {
   3      public:
   4  
   5      DaoCdata *dao_cdata;
   6  
   7      Dao_ClassTwo();
   8      ~Dao_ClassTwo();
   9  
  10      int VirtualMethod( int i, float f );
  11  };
     
这个代理类需要它的封装对象的引用,以便在重新实现的虚函数里可正确地调用 道派生类所重新实现的
虚函数。这里成员dao_cdata即指向它的封装对象。 这个封装对象可在Dao_ClassTwo的构造函数里预先创
建: 
     
   1  Dao_ClassTwo::Dao_ClassTwo()
   2  {
   3      dao_cdata = DaoCdata_New( dao_type_ClassTwo, this );
   4      DaoGC_IncRC( (DaoValue*)dao_cdata );
   5  }
     
这里dao_type_ClassTwo是此封装类型的类型对象, 可以以获得dao_type_ClassOne的同样方式获得。 现
在Dao_ClassTwo有了一个DaoCdata对象的引用。 这个对象的引用计数须作恰当处理,即在构造函数里需
要增加其引用计数, 而在析构函数里需要减少其引用计数。并且此封装类型的类型信息结构里的 
GetGCFields需要适当定义一正确支持垃圾回收处理, 这个将在后面作介绍。 这里先介绍它的析构函数
: 
     
   1  Dao_ClassTwo::~Dao_ClassTwo()
   2  {
   3      if( dao_cdata ){ // 有可能在垃圾回收时被设为NULL:
   4          // 将dao_cdata的C/C++对象指针设为NULL,以避免释放dao_cdata时,
   5          // 道虚拟机将试图释放该指针所指向的内存:
   6          DaoCdata_SetData( dao_cdata, NULL );
   7          DaoGC_DecRC( (DaoValue*) dao_cdata );
   8      }
   9  }
     


先面将介绍这个封装里最复杂的部分,即重新实现被封装类的虚函数: 
     
   1  int Dao_ClassTwo::VirtualMethod( int i, float f )
   2  {
   3      DaoVmSpace *vmspace = DaoVmSpace_MainVmSpace();
   4      DaoProcess *proc = NULL;
   5  
   6      // 获取派生道类的实例:
   7      DaoObject *object = DaoCdata_GetObject( dao_cdata );
   8      if( object == NULL ) goto CallDefault;
   9  
  10      // 获取道派生类所重新实现的名为"VirtualMethod"的方法:
  11      DaoRoutine *method = DaoObject_GetMethod( object, "VirtualMethod" );
  12      if( method == NULL ) goto CallDefault;
  13  
  14      // 检查该方法是否为C/C++封装函数,若是,它表明虚函数没有被重新实现:
  15      if( DaoRoutine_IsWrapper( method ) ) goto CallDefault;
  16  
  17      // 获取一个虚拟机进程对象,以便运行被道派生类重新实现的虚函数:
  18      proc = DaoVmSpace_AcquireProcess( vmspace );
  19      
  20      // 准被虚函数调用的参数:
  21      DaoProcess_NewInteger( proc, i );
  22      DaoProcess_NewFloat( proc, f );
  23      DaoValue **params = DaoProcess_GetLastValues( proc, 2 );
  24  
  25      // 使用参数解析可能有重载的方法:
  26      method = DaoRoutine_ResolveByValue( method, object, params, 2 );
  27      if( method == NULL ) goto CallDefault;
  28  
  29      // 调用重新实现的虚函数:
  30      if( DaoProcess_Call( proc, method, object, params, 2 ) ) goto ErrorCall;
  31  
  32      // 检查虚函数调用的返回值:
  33      DaoValue *res = DaoProcess_GetReturned( proc );
  34      if( DaoValue_CastInteger( res ) ) goto ErrorCall;
  35  
  36      int ires = DaoValue_TryGetInteger( res );
  37  
  38      // 释放进程对象:
  39      DaoProcess_Release( vmspace, proc );
  40  
  41      return ires;
  42  
  43  CallDefault:
  44      if( proc ) DaoProcess_Release( vmspace, proc );
  45      return ClassTwo::VirtualMethod( i, f );
  46  ErrorCall:
  47      DaoProcess_Release( vmspace, proc );
  48      return 0;
  49  }
     


现在我们将定义ClassTwo的封装类型信息结构里的GetGCFields函数: 
     
   1  static void Dao_ClassTwo_GetGCFields( void *self0,
   2          DList *values, DList *lists, DList *maps, int remove )
   3  {
   4      Dao_ClassTwo *self = (Dao_ClassTwo*) self0;
   5      if( self->dao_cdata == NULL ) return;
   6      DList_Append( values, self->dao_cdata );
   7      if( remove ){
   8          // 道语言的垃圾回收器将在不同的阶段调用此函数。如果是在扫瞄候选垃圾
   9          // 对象以确定真实的垃圾期间调用,此函数的参数"remove"将为零。
  10          // 
  11          // 对于真实的垃圾对象,道垃圾回收器将用非零"remove"参数调用此函数。
  12          // 这种情况下,成员"dao_cdata"必须在这里被设为空指针NULL,因为
  13          // "dao_cdata"所指向的对象将被垃圾回收器直接释放,导致"dao_cdata"
  14          // 在此函数调用后变成无效指针。
  15          // 
  16          // 需要特别注意的是,如果有别的东西负责释放被封装的C/C++对象,那么
  17          // 这里需要做些特别处理。如在某些图形界面库里,父控件将负责它的子
  18          // 控件的释放。为了防止道垃圾回收器调用被封装C/C++对象的释放函数,
  19          // 提前或重复释放该C/C++对象,这里可做类似如下的处理:
  20          // if( self->parent() ) DaoCdata_SetData( self->dao_cdata, NULL );
  21  
  22          // 此函数调用后,"dao_cdata"可能会变成无效指针,这里需要将它设为
  23          // 空指针NULL,以防它被错误使用:
  24          self->dao_cdata = NULL;
  25      }
  26  }
     


ClassTwo的剩余封装基本如下: 
     
   1  static void dao_ClassTwo_ClassTwo( DaoProcess *proc, DaoValue *p[], int n )
   2  {
   3      Dao_ClassTwo *self = new Dao_ClassTwo();
   4      DaoProcess_PutValue( proc, (DaoValue*) self->dao_cdata );
   5  }
   6  static DaoFuncItem ClassTwoMethods[] =
   7  {
   8      { dao_ClassTwo_ClassTwo,  "ClassTwo()" },
   9      { NULL, NULL }
  10  };
  11  static void Dao_ClassTwo_Delete( void *self )
  12  {
  13      delete (Dao_ClassTwo*) self;
  14  }
  15  static void* Dao_ClassTwo_Cast_ClassOne( void *data, int down )
  16  {
  17      if( down ) return static_cast<ClassTwo*>((ClassOne*)data);
  18      return dynamic_cast<ClassOne*>((ClassTwo*)data);
  19  }
  20  // ClassTwo 的类型信息结构:
  21  static DaoTypeBase ClassTwo_Typer = 
  22  {
  23      "ClassTwo", NULL, NULL, ClassTwoMethods,
  24      { & ClassOne_Typer, NULL },
  25      { Dao_ClassTwo_Cast_ClassOne, NULL },
  26      Dao_ClassTwo_Delete, NULL
  27  };
  28  // ClassTwo 的类型对象:
  29  DaoType *dao_type_ClassTwo = NULL;
  30  
  31  int DaoOnLoad( DaoVmSpace *vmSpace, DaoNamespace *nspace )
  32  {
  33      ...
  34      // 以非透明方式封装ClassTwo:
  35      dao_type_ClassTwo = DaoNamespace_WrapType( nspace, & ClassTwo_Typer, 1 );
  36      return 0;
  37  }
     


 4   Dao和C/C++类型之间的数据转换     
 4   Dao和C/C++类型之间的数据转换     
 4   Dao和C/C++类型之间的数据转换     

道虚拟机提供了多个C借口函数,以方便Dao和C/C++类型之间的数据转换。 对于简单的类型,下面的函数
可将道数据转换为C数据: 
     
   1  dao_integer DaoValue_TryGetInteger( DaoValue *self );
   2  dao_float   DaoValue_TryGetFloat( DaoValue *self );
   3  dao_complex DaoValue_TryGetComplex( DaoValue *self );
   4  char*     DaoValue_TryGetChars( DaoValue *self );
   5  DString*  DaoValue_TryGetString( DaoValue *self );
   6  int       DaoValue_TryGetEnum( DaoValue *self );
   7  
   8  void*     DaoValue_TryGetCdata( DaoValue *self );
   9  void**    DaoValue_TryGetCdata2( DaoValue *self );
  10  void*     DaoValue_TryCastCdata( DaoValue *self, DaoType *totype );
     
如果DaoValue对象的类型跟函数所假定的一致,正确的数据将被返回; 否则零或者空指针将被返回。 最
后的三个函数仅适用于以非透明方式封装的C/C++类型。

对于其他类型,你将需要先将DaoValue对象指针映射为相应类型的指针后, 才可以用相应的函数访问它
们的数据。 这里有两种方法做这种映射,一种是先用DaoValue_Type()检查对象的类型, 然后再用C方式
做映射。另一种是使用DaoValue_CastXXX()系列方法之一。 下例展示了这两种方法将DaoValue映射成
DaoTuple: 
     
   1  DaoTuple *tup1 = DaoValue_Type( value ) == DAO_TUPLE ? (DaoTuple*) value : NULL;
   2  DaoTuple *tup2 = DaoValue_CastTuple( value );
     
DaoValue_CastXXX()将返回控指针,如果对象的类型和函数所要求的类型 不一致。