[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 abc或load 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_FLOAT 和DAO_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 }; 这里面的成员fpter 和 proto 必须跟以下函数参数里的一致: 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_value和dao_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()将返回控指针,如果对象的类型和函数所要求的类型 不一致。