load help; @[name] daovm.interface @[name] @[title] 使用Dao的C语言接口编程 @[title] load help; ################################################################################ ################################################################################ #### Embedding Dao Virtual Machine ################################################################################ ################################################################################ @[name] daovm.interface.embedding @[name] @[title] 嵌入道虚拟机 @[title] @[text] @[section] 一个简单的例子 @[section] @[subsection] 初始化道运行环境 @[subsection] 在使用道库做任何事之前,@[code]DaoInit()@[code]必须被调用以初始化道运行环境。 @[code(cxx)] // 初始化道: DaoVmSpace *vmspace = DaoInit( NULL ); @[code(cxx)] 这个函数将返回一@[code]DaoVmSpace@[code]对象,该对象可用来载入道脚本或模块。 此函数可以接受一@[code]char*@[code]字符串参数,传入的此参数应当是此程序的路径 和名称,用来确定额外的模块搜寻路径。 @[comment] Before you start to use the @[code]DaoVmSpace@[code] object to run Dao scripts or load Dao modules, you may set some options in the object, e.g: @[code(cxx)] DaoVmSpace_SetOptions( vmspace, DAO_EXEC_DEBUG ); @[code(cxx)] @[comment] @[subsection] 载入脚本文件 @[subsection] 一旦你有了一个@[code]DaoVmSpace@[code]对象,你可以开始运行道脚本或载入道模块了。 如果你有一个主脚本文件,并且你所要做的仅仅是执行这个文件,你可以简单地调用: @[code(cxx)] // 载入并执行 "myscript.dao": DaoVmSpace_Load( vmspace, "myscript.dao" ); @[code(cxx)] 顶层作用域的代码将被执行。 @[subsection] 退出道环境 @[subsection] 在你用完道虚拟机后,你可以调用以下函数来正确地终止道虚拟机并退出道环境。 调用这个这个函数将会等待未完成的线程结束,也会等待垃圾回收器回收完垃圾对象, 并且释放一些内部数据结构等。 @[code(cxx)] // 退出 Dao: DaoQuit(); @[code(cxx)] @[subsection] 将这些代码放在一起 @[subsection] 将这些代码放在一起,嵌入道就如下面的例子简单: @[code(cxx)] // 初始化 Dao: DaoVmSpace *vmspace = DaoInit( NULL ); // 载入运行 "myscript.dao": DaoVmSpace_Load( vmspace, "myscript.dao" ); // 退出 Dao: DaoQuit(); @[code(cxx)] @[section] 一个稍微高级点的例子 @[section] 在道里,每个代码文件在编译后都会被表示为一个命名空间对象, 它存有这个文件里的和一些它载入的文件里的全局常量和变量等。 要做任何实际的事情,你一般都要用到这个命名空间对象。 @[subsection] 获得命名空间对象 @[subsection] 接着上面的例子,如果你想要调用"myscript.dao"里的某个函数, 你可以先保存DaoVmSpace_Load()所返回的命名空间对象: @[code(cxx)] // 载入 "myscript.dao" 并获得命名空间对象: DaoNamespace *nspace = DaoVmSpace_Load( vmspace, "myscript.dao" ); @[code(cxx)] @[subsection] 获取函数对象 @[subsection] 要找到你想调用的函数,使用如下代码: @[code(cxx)] // 查找名为 "myfunction" 的对象: DaoValue *value = DaoNamespace_FindData( nspace, "myfunction" ); // 尝试将它转换为函数对象: DaoRoutine *myfunc = DaoValue_CastRoutine( value ); @[code(cxx)] 如果"myfunction"确实是个函数对象,@[code]myfunc@[code]将不会是空指针。 @[subsection] 获得虚拟机进程对象 @[subsection] 道程序都是以虚拟机进程的方式在道虚拟机上运行。 因此若要调用前面的函数,你将需要一个虚拟机进程对象@[code]DaoProcess@[code]。 这种对象可由直接调用@[code]DaoProcess_New()@[code]生成, 但是更好的方式是从@[code]DaoVmSpace@[code]对象获得, 这种方式更经济,更方便管理。 @[code(cxx)] // 获得虚拟机进程对象: DaoProcess *proc = DaoVmSpace_AcquireProcess( vmspace ); @[code(cxx)] @[subsection] 准备参数值 @[subsection] 现在假定函数"myfunction"需要一个整数作为第一个参数和一个字符串作为第二个参数。 为了调用它,我们将需要先准备好一个道整数对象和一个道字符串对象。 这些对象都可通过它们相应的分配器函数生成,但是更好的方式则是使用 @[code]DaoProcess@[code]类型的“工厂”方法产生。 这些方法一般以@[code]DaoProcess_New@[code]开头。 例如,生成道整数对象和道字符串对象,我们可以用: @[code(cxx)] // 准备道整数对象和道字符串对象: DaoInteger *ivalue = DaoProcess_NewInteger( proc, 123 ); DaoString *svalue = DaoProcess_NewString( proc, "abc", -1 ); @[code(cxx)] The third parameter of @[code]DaoProcess_NewString()@[code]的第三个参数是前面C字符串的长度, 如果为负值,该字符串是以NULL作为结束标识。 这些对象必须放在数组里才能传递给"myfunction"函数: @[code(cxx)] DaoValue *params[2]; params[0] = (DaoValue*) ivalue; params[1] = (DaoValue*) svalue; @[code(cxx)] 因为这些对象是有@[code]DaoProcess@[code]的方法生成的, 你也可以通过下面的以数组得方式获得这些对象: @[code(cxx)] // 获取最后产生的两个对象: DaoValue **params = DaoProcess_GetLastValues( proc, 2 ); @[code(cxx)] @[subsection] 调用道函数 @[subsection] 现在我们已经可以用准备好的参数调用前面获得的函数了: @[code(cxx)] // 调用函数: DaoProcess_Call( proc, myfunc, NULL, params, 2 ); @[code(cxx)] 对于类方法,一个类实例对象可作为该函数的第三个参数传入。 不过它也可作为@[code]params@[code]数组的第一个元素传入。 值得注意的是这个函数能处理重载过的函数,因此你不需要为此做任何事 (当然还是要传入正确的参数对象)。 没有错误的情况下,这个函数将返回零,否则返回一个表示错误的整数。 @[subsection] 获得函数的返回值 @[subsection] 如果上面的函数有返回值,该返回值可通过下面的函数获得: @[code(cxx)] // 获得函数的返回值: DaoValue *retvalue = DaoProcess_GetReturned( proc ); @[code(cxx)] 如果函数只返回某个特定类型的值,那么你可用@[code]DaoValue_CastXXX()@[code] 之类的函数将获得的值转换到该类型。 你也可以使用@[code]DaoValue_TryGetXXX()@[code]将它直接转换到相应的C类型。 例如,如果"myfunction"返回整数,你可通过下面的方式获得。 @[code(cxx)] // 获得返回的整数: daoint retint = DaoValue_TryGetInteger( retvalue ); @[code(cxx)] @[subsection] 释放虚拟进程对象 @[subsection] 当你使用完一个虚拟进程对象后,你需要释放它。 最好的方式是将它是放回@[code]DaoVmSpace@[code]对象,以便重用。 @[code(cxx)] // 释放虚拟进程对象: DaoVmSpace_ReleaseProcess( vmspace, proc ); @[code(cxx)] 但如果你需要频繁地使用一个虚拟进程对象, 你可以保留它直到最后不需要的时候。 不过这样的话,就有必要释放前面准备的参数了: @[code(cxx)] // 释放缓存的参数: DaoProcess_PopValues( proc, 2 ); @[code(cxx)] @[subsection] 将这些代码放在一起 @[subsection] @[code(cxx)] // 载入 "myscript.dao" 并获得命名空间对象: DaoNamespace *nspace = DaoVmSpace_Load( vmspace, "myscript.dao" ); // 查找名为 "myfunction"的对象: DaoValue *value = DaoNamespace_FindData( nspace, "myfunction" ); // 尝试转换为函数对象: DaoRoutine *myfunc = DaoValue_CastRoutine( value ); // 获得进程对象: DaoProcess *proc = DaoVmSpace_AcquireProcess( vmspace ); // 准备一个整数对象和字符串对象: DaoInteger *ivalue = DaoProcess_NewInteger( proc, 123 ); DaoString *svalue = DaoProcess_NewString( proc, "abc", -1 ); // 以数组的形式获得这两个对象: DaoValue **params = DaoProcess_GetLastValues( proc, 2 ); // 调用函数: DaoProcess_Call( proc, myfunc, NULL, params, 2 ); // 获取函数的返回值: DaoValue *retvalue = DaoProcess_GetReturned( proc ); // 获取返回的整数值: daoint retint = DaoValue_TryGetInteger( retvalue ); // 释放进程对象: DaoVmSpace_ReleaseProcess( vmspace, proc ); @[code(cxx)] @[text] ################################################################################ ################################################################################ #### Extending Dao Virtual Machine ################################################################################ ################################################################################ @[name] daovm.interface.extending @[name] @[title] 扩展道虚拟机 @[title] @[text] 帮助条文@[node]daovm.interface.embedding@[node]里的演示表明道虚拟机的嵌入方式极其简单。 这里将演示道虚拟机的扩展方式也极其简单。 因为道语言支持函数参数的类型标注,它可以省去你写很多样板代码和参数类型检查代码的麻烦。 也就是说,给道语言封装C/C++函数要比给其他如Python和Lua之类的语言封装C/C++函数要简单很多。 @[section] 第一个扩展模块例子 @[section] @[subsection] 扩展函数原型 @[subsection] 所有可被道调用的函数都必须使用类似下面的原型: @[code(cxx)] void MyCFunction( DaoProcess *proc, DaoValue *param[], int nparam ) { printf( "Hello Dao!\n" ); } @[code(cxx)] @[subsection] 模块入口函数 @[subsection] 每个道模块都必须提供一个入口函数。下面这个"DaoOnLoad()"是个基本的入口函数名, 后面将会介绍入口函数名也可以包括模块名。 @[code(cxx)] // Entry function for each C/C++ module: int DaoOnLoad( DaoVmSpace *vmspace, DaoNamespace *ns ); @[code(cxx)] 这个函数将在模块载入时被调用,以便模块注册它的函数和类型等。 此函数的第一个参数是一个虚拟机空间@[code]DaoVmSpace@[code]对象。 此对象负责模块的载入和管理。 第二个参数是自动生成的命名空间对象,此对象将代表此模块,因此这个模块的 函数和类型等都应该注册到此命名空间。 如前面提到的,模块入口函数的名称可以包括模块的名称。 假如模块的名字以@[code(cxx)]load abc@[code(cxx)]或@[code(cxx)]load path.abc@[code(cxx)] 的方式出现在模块载入语句中,那么该名称可以如下方式出现在入口函数名中 (并且搜索方式也如下): @[list] == 全小写字母: 例如 @[code]Daoabc_OnLoad@[code]; == 首大写字母: 例如 @[code]DaoAbc_OnLoad@[code]; == 全大写字母: 例如 @[code]DaoABC_OnLoad@[code]; @[list] @[subsection] 函数注册 @[subsection] 下面的函数可用来将一个函数注册到命名空间@[code]DaoNamespace@[code]对象: @[code(cxx)] // 注册单个函数: DaoRoutine* DaoNamespace_WrapFunction( DaoNamespace *self, DaoCFunction fp, const char *proto ); // 注册多个函数: int DaoNamespace_WrapFunctions( DaoNamespace *self, DaoFuncItem *items ); @[code(cxx)] 我们将在这里仅介绍第一个函数,第二个函数将在后面的小节里介绍。 这两个函数的第一个参数都是命名空间@[code]DaoNamespace@[code]对象, 函数将被注册到此对象里。第一个函数的第二个参数是函数指针类型@[code]DaoCFunction@[code], 它的原型跟我们前面准备的函数@[code]MyCFunction@[code]一致; 第三个参数则是被注册函数在那个命名空间里的名称和原型。 下面将把上面的函数@[code]MyCFunction@[code]以"HelloDao()"注册到命名空间"nspace"里: @[code(cxx)] // 注册函数: DaoNamespace_WrapFunction( nspace, MyCFunction, "HelloDao()" ); @[code(cxx)] 那么这个函数将在道里可以一@[code]HelloDao@[code]不带任何参数调用。 @[subsection] 总结 @[subsection] 把上面的放在一起将够成一个最简单的道扩展模块: @[code(cxx)] #include "dao.h" #include "stdio.h" static void MyCFunction( DaoProcess *proc, DaoValue *param[], int nparam ) { printf( "Hello Dao!\n" ); } int DaoOnLoad( DaoVmSpace *vmspace, DaoNamespace *nspace ) { DaoNamespace_WrapFunction( nspace, MyCFunction, "HelloDao()" ); return 0; } @[code(cxx)] 要编译这些代码,你将需要把道的头文件路径加到你的编译选项里。 并且你还需要声明以下预处理器定义: @[list] -- Win32 上: @[code]WIN32@[code]; -- Unix 上: @[code]UNIX@[code]; -- MacOSX 上: @[code]MACOSX@[code]; @[list] 在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, @[list] -- Unix 上: @[code]-rdynamic@[code]; -- MacOSX 上: @[code]-undefined dynamic_lookup@[code]; @[list] 如果你使用DaoMake (@[node]tool.standard.daomake@[node]) 编译你的模块, 这些都将被自动处理好。 @[section] 第二个函数封装例子 @[section] 现在我们将展示如何封装可接受参数和可返回值的函数。 假定我们需要封装以下C函数 @[code] float Test( int id, const char *name, int extra ); @[code] 并给最后的"extra"参数支持一个缺省值。 那么我们将需要封装的函数以下面的原型注册到道里: @[code] # 道函数原型 MyTest( id: int, name: string, extra = 0 ) => float @[code] 这样,此函数将可以接受一个整数和一个字符串作为参数,以及另一个整数作为额外的参数。 这个函数原型也表示此函数将返回一个单精度浮点数。 在C封装函数里,将道数据类型转换到C数据类型的方式很简单,并且把C数据返回给道 的方式也很简单: @[code(cxx)] void MyTestInC( DaoProcess *proc, DaoValue *param[], int nparam ) { daoint id = param[0]->xInteger.value; char *name = param[1]->xString.value->chars; daoint extra = param[2]->xInteger.value; DaoProcess_PutFloat( proc, Test( id, name, extra ) ); } @[code(cxx)] 正如你所看到的,你不需要检查参数个数或类型,就可以直接得到参数的数据。 当道程序调用这个函数时,它保证将有正确类型的参数值传递给这个函数。 但为了使用上述数据转换方式,你将需要在你的模块源文件里包含其他道虚拟机的头文件 (如@[code]daoValue.h@[code])。 并且你还需要对道语言的内部标准数据结构有所了解。 如果你只想使用单一的@[code]dao.h@[code]头文件,你将需要使用那些 @[code]DaoValue_TryGetXXX()@[code]函数。这些函数将以很小的额外开销 作最少的类型检查,并返回适当的C数据。 @[code(cxx)] void MyTestInC( DaoProcess *proc, DaoValue *param[], int nparam ) { daoint id = DaoValue_TryGetInteger( param[0] ); char *name = DaoValue_TryGetChars( param[1] ); daoint extra = DaoValue_TryGetInteger( param[2] ); DaoProcess_PutFloat( proc, Test( id, name, extra ) ); } @[code(cxx)] 如果你是以合适的道函数原型注册了这个函数,那些@[code]DaoValue_TryGetXXX()@[code] 里的类型检查都将成功,获得的都是正确的数据。 函数@[code]DaoProcess_PutFloat()@[code] 是用来从C封装函数里返回一个单精度浮点数 给道程序。这个浮点数将被放在到虚拟进程上合适的地方。 详情请看以下小节。 现在这个函数可被注册为: @[code(cxx)] // 注册一个带参数和返回值的函数: DaoNamespace_WrapFunction( nspace, MyTestInC, "MyTest(id:int,name:string,extra=0)=>float" ); @[code(cxx)] @[section] C/C++类型的基本封装 @[section] 有两种使用C/C++类型来扩展道的方式。一种是不透明类型封装, 这种封装以不透明指针的方式访问被封装的C/C++数据结构或对象。 这是封装已有的C/C++类型的标准方式。 另一种是定义用户定制的C类型,这种类型实际是以道虚拟机可以访问的 方式定义C数据结构。 对于非透明的封装,道虚拟机内部数据结构@[code]DaoCdata@[code] 将被用来表示被封装的C/C++数据。而对每个用户定制的C类型, 则需要定义对应的数据结构。因此定义用户定制的C类型可能会稍微 复杂一点。不过这两种扩展类型在道虚拟机里的注册方式基本一致。 这里我将先介绍不透明的C/C++类型封装。 @[subsection] 类型信息结构 @[subsection] C/C++扩展类型只有在道语言的命名空间注册后才可被使用。 要注册扩展类型,必须先定义一个类型信息结构@[code]DaoTypeBase@[code], 这样它才能以下面的方式注册: @[code(cxx)] DaoType* DaoNamespace_WrapType( DaoNamespace *self, DaoTypeBase *typer, int opaque ); int DaoNamespace_WrapTypes( DaoNamespace *self, DaoTypeBase *typer[] ); @[code(cxx)] 这里@[code]opaque@[code]参数必须是0或者1:0表示透明封装(即用户定制)类型; 1则表示非透明封装。 @[subsubsection]类型信息结构的定义@[subsubsection] 下面是类型信息结构@[code]DaoTypeBase@[code]的定义: @[code(cxx)] /* 用于创建C/C++扩展类型的类型信息结构: */ struct DaoTypeBase { const char *name; /* 类型名; */ DaoTypeCore *core; /* 类型内部数据; */ DaoNumItem *numItems; /* 成员常数列表(空元素结束); */ DaoFuncItem *funcItems; /* 成员方法类表(空元素结束); */ /* 用于实现扩展类型的继承关系的类型信息结构数组: */ DaoTypeBase *supers[ DAO_MAX_CDATA_SUPER ]; /* 用于将被封装的C/C++对象在派生类型和基类型做转换的函数指针数组: */ FuncPtrCast casts[ DAO_MAX_CDATA_SUPER ]; /* 用来释放C/C++数据对象的内存的函数: */ void (*Delete)( void *self ); /* 用来获取可垃圾回收的成员对象的函数: */ void (*GetGCFields)( void *self, DList *values, DList *lists, DList *maps, int remove ); }; @[code(cxx)] 这个结构可用来定义创建道语言的扩展类型所需的信息。 显然每个扩展类型都需要一个名称,此名称可在该结构的一个成员里设定。 该结构的第二个成员则是为内部使用所预留。它的其他成员将在接下来的小结里作介绍。 @[subsubsection]成员常数和方法@[subsubsection] @[code]DaoTypeBase@[code]结构的第三个成员@[code]numItems@[code]可用来声明 扩展类型的一组常数成员。常数的声明使用了以下结构: @[code(cxx)] struct DaoNumItem { const char *name; /* 常数名; */ int type; /* 常数类型; */ double value; /* 常数值; */ }; @[code(cxx)] 这里常数类型必须是@[code]DAO_INTEGER@[code], @[code]DAO_FLOAT@[code] 和@[code]DAO_DOUBLE@[code] 之一。 这个数组必须以一个带空名称成员的结构结束。 例如: @[code(cxx)] static DaoNumItem myTypeNumbers[] = { { "MODE_ONE", DAO_INTEGER, MODE_ONE }, { "MODE_TWO", DAO_INTEGER, MODE_TWO }, { NULL, 0, 0 } }; @[code(cxx)] 如果括在类型没有成员常量,成员@[code]numItems@[code]则可为空指针。 @[code]DaoTypeBase@[code]结构的第四个成员@[code]funcItems@[code] 则可用来声明扩展类型的构造方法和成员方法。 方法的声明需使用以下结构: @[code(cxx)] struct DaoFuncItem { DaoCFunction fpter; /* 方法的C函数指针; */ const char *proto; /* 方法的原型,如: name( parlist ) => return_type */ }; @[code(cxx)] 这里面的成员@[code]fpter@[code] 和 @[code]proto@[code] 必须跟以下函数参数里的一致: @[code(cxx)] DaoRoutine* DaoNamespace_WrapFunction( DaoNamespace *self, DaoCFunction fp, const char *proto ); @[code(cxx)] @[subsubsection]基类和类型转换函数@[subsubsection] @[code]DaoTypeBase@[code]结构的第三个成员@[code]supers@[code]可以用来 定义扩展类型间的继承关系。定义这种继承关系只需在@[code]supers@[code]数组里 设定基类的类型信息结构,并以空指针结束该数组。 如果被封装的的类型是带虚函数或虚基类的C++类型,道语言将需要知道如何正确地 在派生类型和基类间做转换/映射。 这可通过在成员数组@[code]casts@[code]里设定能做正确的 映射或转换的函数。如果该C++类型有虚函数而没有虚基类,这些函数一般应该按以下方式实现: @[code(cxx)] void* cast_Sub_Base( void *data, int down_casting ) { if( down_casting ) return static_cast( (Base*)data ); return dynamic_cast( (Sub*)data ); } @[code(cxx)] 而如果该C++类型有虚基类,则这些函数一般应该按以下方式实现: @[code(cxx)] void* cast_Sub_Base( void *data, int down_casting ) { if( down_casting ) return dynamic_cast( (Base*)data ); return dynamic_cast( (Sub*)data ); } @[code(cxx)] @[subsubsection]内存释放和垃圾回收@[subsubsection] 如果被封装的C/C++类型的数据不能直接由C标准函数@[code]free()@[code]释放, 那么@[code]DaoTypeBase@[code]结构的@[code]Delete@[code]成员函数指针必须 指向一个用户定义的适用于该类型的内存释放函数。 对于非透明封装类型,该类型数据的非透明指针将被作为@[code]self@[code]参数传递给该函数; 而对于用户定制的扩展类型,该类型的整个数据结构指针将被传递给该释放函数。 如果一个扩展类型包含了使用道引用计数的成员数据,并且可能形成循环引用, 那么成员函数指针@[code]GetGCFields@[code]也需要指向一个恰当定义的函数。 这个函数的主要目的是将扩展类型里可导致循环引用的成员暴露个道的垃圾回收器。 这函数的基本原型如下: @[code(cxx)] void GetGCFields( void *self, DList *values, DList *lists, DList *maps, int remove ); @[code(cxx)] 在这个函数里,被该扩展类型直接引用的成员因该被推入到@[code]values@[code]列表; 而那些元素带引用计数的@[code]DList@[code]成员则应推入到@[code]lists@[code]列表里; 那些元素带引用计数的@[code]DMap@[code]成员则应推入到@[code]maps@[code]列表里。 这个函数可以在垃圾回收的不同阶段,用不同的@[code]remove@[code]参数调用。 在扫瞄候选垃圾对象以确定真实的垃圾对象的阶段调用该函数时,@[code]remove@[code]将为零。 而在垃圾对象的内存释放阶段,@[code]remove@[code]参数将为一, 这种情况下,该函数必须把被推入@[code]values@[code]的成员指针置空。 因为推入@[code]values@[code]里的成员对象将被垃圾回收器直接释放, 将那些成员重设空指针可防止可能的双重释放。 @[subsection]一个简单的例子@[subsection] 给定如下C++类: @[code(cxx)] class ClassOne { public: int value; enum { CLASSONE_AA, CLASSONE_BB }; ClassOne( int v ); int Method( const char *s ); }; @[code(cxx)] 它可按如下方式封装: @[code(cxx)] // 先声明封装函数: static void dao_ClassOne_ClassOne( DaoProcess *proc, DaoValue *p[], int n ); static void dao_ClassOne_Method( DaoProcess *proc, DaoValue *p[], int n ); // 声明常数成员以封装其成员枚举值: static DaoNumItem ClassOneNumbers[] = { { "CLASSONE_AA", DAO_INTEGER, CLASSONE_AA }, { "CLASSONE_BB", DAO_INTEGER, CLASSONE_BB }, { NULL, 0, 0 } }; // 声明构造方法和成员方法: static DaoFuncItem ClassOneMethods[] = { // 跟类型名同名的方法将作构造方法处理: { dao_ClassOne_ClassOne, "ClassOne( v: int )" }, { dao_ClassOne_Method, "Method( self: ClassOne, s: string ) => int" }, { NULL, NULL } }; static void ClassOne_Delete( void *self ) { delete (ClassOne*) self; } // ClassOne的类型信息结构: static DaoTypeBase ClassOne_Typer = { "ClassOne", NULL, ClassOneNumbers, ClassOneMethods, {NULL}, {NULL}, ClassOne_Delete, NULL }; // ClassOne的类型结构: DaoType *dao_type_ClassOne = NULL; static void dao_ClassOne_ClassOne( DaoProcess *proc, DaoValue *p[], int n ) { // 获得整型参数; daoint v = DaoValue_TryGetInteger( p[0] ); // 创建ClassOne实例: ClassOne *self = new ClassOne( v ); // 返回用dao_type_ClassOne类型封装的实例: DaoProcess_PutCdata( proc, self, dao_type_ClassOne ); } static void dao_ClassOne_Method( DaoProcess *proc, DaoValue *p[], int n ) { // 获得类型实例: ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne ); // 获得字符串参数: char *s = DaoValue_TryGetChars( p[1] ); int res = self->Method( s ); // 返回整数结果: DaoProcess_PutInteger( proc, res ); } int DaoOnLoad( DaoVmSpace *vmSpace, DaoNamespace *nspace ) { // 将ClassOne注册为非透明封装类型: dao_type_ClassOne = DaoNamespace_WrapType( nspace, & ClassOne_Typer, 1 ); return 0; } @[code(cxx)] 因为@[code]ClassOne@[code]的成员@[code]value@[code]为公共访问成员, @[code]ClassOne@[code]的完整封装类型应当包含属性方法以支持对该成员的直接访问。 这种属性方法的定义即为对成员访问的操作符@[code].field@[code]和@[code].field=@[code]的重载。 例如,对于成员@[code]value@[code],下面的方法将使得它可以象公共成员那样使用。 @[code(cxx)] // the getter and setter: { dao_ClassOne_GETF_value, ".value( self: ClassOne ) => int" }, { dao_ClassOne_SETF_value, ".value=( self: ClassOne, value: int )" }, @[code(cxx)] 这里函数名称@[code]dao_ClassOne_GETF_value@[code]和@[code]dao_ClassOne_SETF_value@[code] 可以随便设定。它们的基本实现如下: @[code(cxx)] static void dao_ClassOne_GETF_value( DaoProcess *proc, DaoValue *p[], int n ) { ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne ); DaoProcess_PutInteger( proc, self->value ); } static void dao_ClassOne_SETF_value( DaoProcess *proc, DaoValue *p[], int n ) { ClassOne *self = (ClassOne*) DaoValue_TryCastCdata( p[0], dao_type_ClassOne ); self->value = DaoValue_TryGetInteger( p[1] ); } @[code(cxx)] @[subsection]一个更高级的例子@[subsection] 在这个更高级的例子里我们将看到对继承和虚函数的封装处理。 这里我们有一个从@[code]ClassOne@[code]派生的类: @[code(cxx)] class ClassTwo : public ClassOne { public: virtual void VirtualMethod( int i, float f ); }; @[code(cxx)] 因为这个类含有虚函数,如果我们需要支持从此类的封装类型派生的道语言类可以 重新实现这个虚函数,那么这个类的封装将会复杂些。 首先我们需要定义一个代理类,这个代理类将从我们要封装的类即@[code]ClassTwo@[code] 派生,并要重新实现它的虚函数。这个重新实现的虚函数将负责检查调用此虚函数的类实例 是否源自于该封装类型的道派生类型,且是否重新实现了该虚函数。 若是,这个代理类的虚函数将调用道派生类所重新实现的虚函数, 否则它将调用基类@[code]ClassTwo@[code]的虚函数。 当这个封装类型的实例被创建时,实际被创建的C++类实例将是该类的代理类的实例。 下面是一个例子代理类: @[code(cxx)] class Dao_ClassTwo : public ClassTwo { public: DaoCdata *dao_cdata; Dao_ClassTwo(); ~Dao_ClassTwo(); int VirtualMethod( int i, float f ); }; @[code(cxx)] 这个代理类需要它的封装对象的引用,以便在重新实现的虚函数里可正确地调用 道派生类所重新实现的虚函数。这里成员@[code]dao_cdata@[code]即指向它的封装对象。 这个封装对象可在@[code]Dao_ClassTwo@[code]的构造函数里预先创建: @[code(cxx)] Dao_ClassTwo::Dao_ClassTwo() { dao_cdata = DaoCdata_New( dao_type_ClassTwo, this ); DaoGC_IncRC( (DaoValue*)dao_cdata ); } @[code(cxx)] 这里@[code]dao_type_ClassTwo@[code]是此封装类型的类型对象, 可以以获得@[code]dao_type_ClassOne@[code]的同样方式获得。 现在@[code]Dao_ClassTwo@[code]有了一个@[code]DaoCdata@[code]对象的引用。 这个对象的引用计数须作恰当处理,即在构造函数里需要增加其引用计数, 而在析构函数里需要减少其引用计数。并且此封装类型的类型信息结构里的 @[code]GetGCFields@[code]需要适当定义一正确支持垃圾回收处理, 这个将在后面作介绍。 这里先介绍它的析构函数: @[code(cxx)] Dao_ClassTwo::~Dao_ClassTwo() { if( dao_cdata ){ // 有可能在垃圾回收时被设为NULL: // 将dao_cdata的C/C++对象指针设为NULL,以避免释放dao_cdata时, // 道虚拟机将试图释放该指针所指向的内存: DaoCdata_SetData( dao_cdata, NULL ); DaoGC_DecRC( (DaoValue*) dao_cdata ); } } @[code(cxx)] 先面将介绍这个封装里最复杂的部分,即重新实现被封装类的虚函数: @[code(cxx)] int Dao_ClassTwo::VirtualMethod( int i, float f ) { DaoVmSpace *vmspace = DaoVmSpace_MainVmSpace(); DaoProcess *proc = NULL; // 获取派生道类的实例: DaoObject *object = DaoCdata_GetObject( dao_cdata ); if( object == NULL ) goto CallDefault; // 获取道派生类所重新实现的名为"VirtualMethod"的方法: DaoRoutine *method = DaoObject_GetMethod( object, "VirtualMethod" ); if( method == NULL ) goto CallDefault; // 检查该方法是否为C/C++封装函数,若是,它表明虚函数没有被重新实现: if( DaoRoutine_IsWrapper( method ) ) goto CallDefault; // 获取一个虚拟机进程对象,以便运行被道派生类重新实现的虚函数: proc = DaoVmSpace_AcquireProcess( vmspace ); // 准被虚函数调用的参数: DaoProcess_NewInteger( proc, i ); DaoProcess_NewFloat( proc, f ); DaoValue **params = DaoProcess_GetLastValues( proc, 2 ); // 使用参数解析可能有重载的方法: method = DaoRoutine_ResolveByValue( method, object, params, 2 ); if( method == NULL ) goto CallDefault; // 调用重新实现的虚函数: if( DaoProcess_Call( proc, method, object, params, 2 ) ) goto ErrorCall; // 检查虚函数调用的返回值: DaoValue *res = DaoProcess_GetReturned( proc ); if( DaoValue_CastInteger( res ) ) goto ErrorCall; int ires = DaoValue_TryGetInteger( res ); // 释放进程对象: DaoProcess_Release( vmspace, proc ); return ires; CallDefault: if( proc ) DaoProcess_Release( vmspace, proc ); return ClassTwo::VirtualMethod( i, f ); ErrorCall: DaoProcess_Release( vmspace, proc ); return 0; } @[code(cxx)] 现在我们将定义@[code]ClassTwo@[code]的封装类型信息结构里的@[code]GetGCFields@[code]函数: @[code(cxx)] static void Dao_ClassTwo_GetGCFields( void *self0, DList *values, DList *lists, DList *maps, int remove ) { Dao_ClassTwo *self = (Dao_ClassTwo*) self0; if( self->dao_cdata == NULL ) return; DList_Append( values, self->dao_cdata ); if( remove ){ // 道语言的垃圾回收器将在不同的阶段调用此函数。如果是在扫瞄候选垃圾 // 对象以确定真实的垃圾期间调用,此函数的参数"remove"将为零。 // // 对于真实的垃圾对象,道垃圾回收器将用非零"remove"参数调用此函数。 // 这种情况下,成员"dao_cdata"必须在这里被设为空指针NULL,因为 // "dao_cdata"所指向的对象将被垃圾回收器直接释放,导致"dao_cdata" // 在此函数调用后变成无效指针。 // // 需要特别注意的是,如果有别的东西负责释放被封装的C/C++对象,那么 // 这里需要做些特别处理。如在某些图形界面库里,父控件将负责它的子 // 控件的释放。为了防止道垃圾回收器调用被封装C/C++对象的释放函数, // 提前或重复释放该C/C++对象,这里可做类似如下的处理: // if( self->parent() ) DaoCdata_SetData( self->dao_cdata, NULL ); // 此函数调用后,"dao_cdata"可能会变成无效指针,这里需要将它设为 // 空指针NULL,以防它被错误使用: self->dao_cdata = NULL; } } @[code(cxx)] @[code]ClassTwo@[code]的剩余封装基本如下: @[code(cxx)] static void dao_ClassTwo_ClassTwo( DaoProcess *proc, DaoValue *p[], int n ) { Dao_ClassTwo *self = new Dao_ClassTwo(); DaoProcess_PutValue( proc, (DaoValue*) self->dao_cdata ); } static DaoFuncItem ClassTwoMethods[] = { { dao_ClassTwo_ClassTwo, "ClassTwo()" }, { NULL, NULL } }; static void Dao_ClassTwo_Delete( void *self ) { delete (Dao_ClassTwo*) self; } static void* Dao_ClassTwo_Cast_ClassOne( void *data, int down ) { if( down ) return static_cast((ClassOne*)data); return dynamic_cast((ClassTwo*)data); } // ClassTwo 的类型信息结构: static DaoTypeBase ClassTwo_Typer = { "ClassTwo", NULL, NULL, ClassTwoMethods, { & ClassOne_Typer, NULL }, { Dao_ClassTwo_Cast_ClassOne, NULL }, Dao_ClassTwo_Delete, NULL }; // ClassTwo 的类型对象: DaoType *dao_type_ClassTwo = NULL; int DaoOnLoad( DaoVmSpace *vmSpace, DaoNamespace *nspace ) { ... // 以非透明方式封装ClassTwo: dao_type_ClassTwo = DaoNamespace_WrapType( nspace, & ClassTwo_Typer, 1 ); return 0; } @[code(cxx)] @[section] Dao和C/C++类型之间的数据转换 @[section] 道虚拟机提供了多个C借口函数,以方便Dao和C/C++类型之间的数据转换。 对于简单的类型,下面的函数可将道数据转换为C数据: @[code(cxx)] dao_integer DaoValue_TryGetInteger( DaoValue *self ); dao_float DaoValue_TryGetFloat( DaoValue *self ); dao_complex DaoValue_TryGetComplex( DaoValue *self ); char* DaoValue_TryGetChars( DaoValue *self ); DString* DaoValue_TryGetString( DaoValue *self ); int DaoValue_TryGetEnum( DaoValue *self ); void* DaoValue_TryGetCdata( DaoValue *self ); void** DaoValue_TryGetCdata2( DaoValue *self ); void* DaoValue_TryCastCdata( DaoValue *self, DaoType *totype ); @[code(cxx)] 如果@[code]DaoValue@[code]对象的类型跟函数所假定的一致,正确的数据将被返回; 否则零或者空指针将被返回。 最后的三个函数仅适用于以非透明方式封装的C/C++类型。 对于其他类型,你将需要先将@[code]DaoValue@[code]对象指针映射为相应类型的指针后, 才可以用相应的函数访问它们的数据。 这里有两种方法做这种映射,一种是先用@[code]DaoValue_Type()@[code]检查对象的类型, 然后再用C方式做映射。另一种是使用@[code]DaoValue_CastXXX()@[code]系列方法之一。 下例展示了这两种方法将@[code]DaoValue@[code]映射成@[code]DaoTuple@[code]: @[code(cxx)] DaoTuple *tup1 = DaoValue_Type( value ) == DAO_TUPLE ? (DaoTuple*) value : NULL; DaoTuple *tup2 = DaoValue_CastTuple( value ); @[code(cxx)] @[code]DaoValue_CastXXX()@[code]将返回控指针,如果对象的类型和函数所要求的类型 不一致。 @[text]