图片 1

零、前言:

GDNative的架构从最先叫“DLScript”的时候到近来结束已经发生了十分的大的变化。随着Godot
3.0本子临近尾声发布以及API越来越稳固,是时候对GDNative近来的造型作多个概述了。

  该篇博客的Title原布署是“在VC++中调用libmemcached的宏图手艺”,可结果却事与原违,原因非常粗大略,移植退步了。即使结果如此,不过这3天的交给却是非常值得的,原因也很简短,收获比比较大。事实上,小编早已在11月份的时候成功移植了当时的最新版本0.49,并写出了上边包车型客车博客:

GDNATIVELIBRARY

GDNativeLibrary是一种财富类型。它是对各样平台所需的骨子里二进制文件的一种浮泛:包涵部分本性、“入口”库加载路线清单及“入口”库所凭借库的清单。

那么些清单是一套功用特色标识的简易映射格局 –
一般是二个文件路线;借使有依附关系的话,正是一组路线。

  

特色标识

Godot有一套脾气标志系统。个性标志表示具备相应的一定的属性或效益,举个例子Windows,
X11, 32, 64,
mobile等等。在导骑行戏时,你也足以自行定义标识,进而恐怕改换游戏的运营格局。

越多关于个性标志的信息,能够去http://docs.godotengine.org/en/latest/learning/workflow/export/feature\_tags.html查看。

GDNativeLibrary财富中的列表由键值对方式整合,键中依据必要能够富含四个特征标识,以英语句点“.”分隔。

比方一个支撑62个人Linux机器的库,它的键名即“X11.64”,若是对应的是Windows的机器,则键名叫“Windows.64”。

Godot编辑器提供了GUI来更人性化的扩充这种财富的定义和编写制定。

图片 2

它会从上而下的对富有入口进行检验,并跳过那多少个不设有的风味标志。在有着可用的记号中,第4个会被看做入口,所以排序很要紧。

  本次移植的指标特别分明,正是基于上次的阅历,对libmemcached实行基于C++的包裹,以便其能够更加好的合併到自家的底层服务框架中,使自己的程序在Windows平台也足以共享memcached服务器带来的性质优势。带着那份憧憬初叶了自己的艰苦移植进度。首先需求证明的是0.49和流行版0.53里面包车型客车歧异是那一个大的,这点也让自己出人意表,因而走了一些弯路,亏伏贴时做出调治,才没有拖延更多的时光去证美赞臣些不或然的事体。下边是自家在入手移植libmemcached从前的布置性思路,移植进程中遇见的难题,以及移植战败后的经验总括。

SINGLETON 库

GDNativeLibrary中有三个属性是用于定义其是或不是援助单例格局利用的。单例库会在Godot运维时期尽量早地载入,且会调用库中的gdnative_singleton函数。这种库常用于要求提供与Godot紧凑结合的作用。

一、最先的筹算思路:

GDNATIVE

GDNative对象表示所载入的库,至于具体要加载哪个库将要从GDNativeLibrary资源文件中相配了,Godot情况下的C++代码能够去调用该库中的函数。由于这种方法去调用函数太过灵敏、底层且不安全,所以是不提议从GDScript这个脚本语言中去调的。

一旦真想从脚本语言碰到一向调用相应功效,能够用GDNative.call_native方法来满意急需。对于这种函数指针调用的尾部细节,抽象出了一种所谓的“调用类型”来打开描述。近些日子只有一种预约义调用项目:standard_varcall

  • 渴求被调用的函数具名称为
    godot_variant function_name(godot_array *)。单例库能够按需注册新的调用类型。

  为啥不在VC中中央银行政机关接调用编写翻译后的libmemcached库呢?原因非常轻便,我们无可奈何直接调用。由于目的库(libmemcached)是在Mingw32遇到下通过gcc编写翻译的,而gcc在Windows下编写翻译动态链接库时(DLL)并不曾转换对应的lib文件,那样在VC中也就不能透过静态链接动态库的艺术将libmemcached链接到调用程序中。那样我们只好采用Windows中提供的另一种方式,即通过LoadLibrary和GetProcAddress等Win32
API来动态加载该动态库,因为该办法不供给.lib文件。固然该办法在本事上被以为是只怕行的,但是libmemcached中设有大气的导出函数,以及那么些函数所依靠的结构体(struct)。为了确定保障GetProcAddress重回的函数指针能够不奇怪的被调用,如memcached_create等,大家只还好日前工程中定义(typedef)大气的函数指针以及有关的结构体。鉴于在此之前的移植经验,由于这几个构造体嵌套了汪洋的内部子结构体,因而假设全数概念就须求多量的劳作。而那么些结构体的分子相当多都是用以libmemcached内部,由此只要在以后的版本中期维修改了该结构体的成员,那么大家的次第也只可以要跟着变动,能够想像,那样的贰只是一对一痛心的。除外,还会有一个极其沉重的缺点,即结构体成员的字节对齐难题。假设不是一字节对齐,如VC缺省的8字节对齐,那么gcc和VC编写翻译器在拍卖此类难题时就恐怕存在一定的异样,一旦那样,VC中定义的结构体填充的数码就无法被gcc准确的抽出,进而导致libmemcached中等学校函授数没办法平时的劳作。

GDNATIVE/GODOT API

设若有些库想调用Godot的有的作用,它就必要去调用Godot的代码。而种种C++编写翻译器之间的移植性特别有标题,所以咱们挑选用C语言API的款型来封装对C++的调用。那开启了三种语言访谈API的也许性,但也拉动了有个别冗余性。

  为了防止上述难点的产生,上边作者就来介绍一种通用的绸缪技能用于缓和此类难点。

API 结构

三个库为了访谈那些用C封装的函数,它首先要了然那几个函数的任务。最直白的主见是留空,然后让操作系统的库加运载飞机制来拍卖。

噩运的是,这种形式不能够在享有平台平常运营(此处Windows或者要窘迫的咳两声),所认为了保险在富有平台安装GDNative库用一样的代码和手续,我们决定运用另一种门路:在加载函数时,以函数指针结构(struct)的款型传递。

该协会存在于Godot中,并蕴藏版本新闻、现在的API更改字段及扩充API列表。

struct godot_gdnative_api_struct {
    unsigned int type;
    godot_gdnative_api_version version;
    const godot_gdnative_api_struct *next;
};

struct godot_gdnative_core_api_struct {
    unsigned int type;
    godot_gdnative_api_version version;
    const godot_gdnative_api_struct *next;
    unsigned int num_extensions;
const godot_gdnative_api_struct **extensions;
    // ...
};

库能够从这种struct中拜望所需的函数,也就表示不再是编写制定
godot_some_function();这种样式了,而是api->godot_some_function();

某一个人喜好轻松的通过函数名并非struct来拜望函数,所以在有亟待时,Godot的构建系统会转移二个静态库,来包裹全数的同名函数指针为静态函数。

  1.
定义一个C++的纯虚接口和两个C的导出函数,个中C++的纯虚接口用于之后的次序调用,该接口中定义了有个别libmemcached中提供的效率,如add、set、replace、get、delete和CAS等。不过要求小心的是该接口中绝非富含或揭破任何和libmemcached相关的音讯,如memcached_st结构体、memcached_create函数等。见如下代码:

扩展

GDNative 增添是一种给库提供GDNative/Godot API
之外作用的方法。它们得以分裂方法使用,上面会列出二种当前支撑的款式的扩张。

推而广之日常带有C语言API,或然还伴随着有自定义数据类型。Godot里一般有用于包裹这一个和别的功用紧凑结合的C函数的C++类/方法。

每一种增添都有它本身的子API结构,在那之中储存了版本音讯及前景API修改音讯的字段。

 1 class MemcachedClientWrapper
 2 {
 3 protected:
 4     virtual ~MemcachedClientWrapper() {}
 5 
 6 public:
 7     virtual bool initialize(bool consistentHash = false, bool supportCAS = false) = 0;
 8     virtual int addServer(const char* serverIP, const int port) = 0;
 9     virtual void release() = 0;
10     virtual bool set(void* key,int klength,void* data,int dlength) = 0;
11     virtual bool add(void* key,int klength,void* data,int dlength) = 0;
12     virtual bool replace(void* key,int klength,void* data,int dlength) = 0;
13     virtual bool append(void* key,int klength,void* data,int dlength) = 0;
14     virtual bool get(void* key,int klength,MCFetchedData*& fetchedData) = 0;
15     virtual bool gets(void** keys,int* keysLength,int count) = 0;
16     virtual bool fetchNext(MCFetchedData*& fetchedData,bool* isEof = 0) = 0;
17     virtual bool remove(void* key,int klength) = 0;
18     virtual bool exists(void* key,int klength,bool* ok = 0) = 0;
19     virtual bool updateWithCAS(void* key,int klength,void* data,int dlength) = 0;
20     virtual bool updateWithAtomicIncrement(void* key,int klength,int step
21             ,uint64& value,int* defaultValue = 0) = 0;
22     virtual bool updateWithAtomicDecrement(void* key,int klength,int step
23             ,uint64& value,int* defaultValue = 0) = 0;
24     virtual bool clean() = 0;
25 };

ARVR

接纳GDNative来贯彻一种VLX570驱动的享有API能够参照文书档案: file。

那套API的源点是 godot_arvr_register_interface
函数,它供给从四个单例库实行调用。这个要被Godot调用的函数则集体成多少个布局以参数的格局传递过去。

目前有 null-driver 的实现、 OpenVR 的实现 和 WIP OpenHMD 的实现。

  四个导出的C函数首要用来创设该接口的兑现子类,以及在应用达成后获释该接口的指针,进而有限支撑能源自由的可相信性,见如下代码:

NATIVESCRIPT

GDNative的最早开辟生涯里,它仅被安顿用于脚本化编制程序,后来被开采出更加的多灵活和有效的地方,脚本化编制程序本领今后但是是中间八个扩张。

NativeScript 完毕了一套“脚本语言” –
在Godot中得以这么叫,但事实上是用GDNative库而不是像GDScript那样的文件和文件的花样来保存有关逻辑。

NativeScript会调用库中的叁个函数 nativescript_init
告知Godot哪些类和措施是可用的。在要用到那个类和艺术的时候,NativeScript就能够非常的粗略的去调用那么些库来落成相应效能。

因为 NativeScript
仅对库开展操作,它并不体贴那几个库是用怎么样语言创设的,若是开垦者要用自个儿疼爱的编制程序语言举行库的支出,就使得
NativeScript 成为 Godot
里的一种最棒选取,纵然在这么些基础上还要付出良多全力。

那想要更加灵敏且更像脚本的感到的话,就应该考虑用一下 PluginScript 了。

1 #define WRAPPER_CC __attribute__((cdcel))
2 
3 extern "C" {
4     //工厂方法创建Wrapper的实现子类,但是返回接口的指针
5     MemcachedClientWrapper* WRAPPER_CC createMCWrapper();
6     //资源释放函数,通过上面函数返回的接口指针,需要通过该函数释放
7     void WRAPPER_CC releaseMCWrapper(MemcachedClientWrapper*);
8 }

PLUGINSCRIPT

PluginScript也是三个扩充,它给Godot参与了封装脚本语言达成的表征。对Godot而言,它是一种运转突出且完全集成的脚本语言,但具备逻辑都以在三个库中贯彻的。

NativeScript
把库都当作脚本用,而PluginScript是用库来定义脚本。约等于一旦在你的Godot项目中添加一些文本,就足以增添一种新的脚本语言支持。

方今截至,这种“野生”的显要运用还唯有二个 godot-python项目。

与ALacrosseVCR-V扩展类似,PluginScript的API也是分外精美,独有贰个亟需调用的函数
godot_pluginscript_register_language。该函数接受四个struct作为参数,struct里包括函数指针及脚本语言的其余新闻。

Godot编辑重视启后,就能够见效了。

     这里之所以采纳cdecl的调用规范,实际不是Windows
API常用的stdcall,主假如因为gcc在编写翻译基于stdcall调用标准的C导出函数时,在导出函数名的前边增添了一个@和该函数参数所占的字节数作为后缀,因而当大家经过GetProcAddress传入函数名获得函数指针时,由于名称不合作,所以回来的函数指针将为NULL。和stdcall不相同的是,基于cdcel调用规范生成的导出函数荒诞不经这么的难题。大家得以经过Windows提供的Dependency工具予以申明。

计划

作者们正在布置创制越多的庞大,如可插拔式音录制解码器。

对于GDNative当前的架构,大家早就至极令人满意了,下一步关键是完美文书档案和改良语言绑定。

  2.
概念一个贯彻类承接自上边定义的纯虚接口,该类将蕴含大量和libmemcached相关的内情新闻,同期也须要在此类中include和libmemcached相关的头文件,并非再自行重新定义和libmemcached相关的细节音讯,最终再通过动态加载的主意实行加载,见如下代码注明:

 1 #include <MemcachedClientWrapper.h>
 2 #include <libmemcached/memcached.h>
 3 
 4 class MemcachedClientWrapperImpl : public MemcachedClientWrapper
 5 {
 6 public:
 7     MemcachedClientWrapperImpl();
 8     virtual ~MemcachedClientWrapperImpl();
 9 
10 public:
11     virtual bool initialize(bool consistentHash = false, bool supportCAS = false);
12     void release();
13     int addServer(const char* serverIP, const int port);
14     bool set(void* key,int klength,void* data,int dlength);
15     bool add(void* key,int klength,void* data,int dlength);
16     bool replace(void* key,int klength,void* data,int dlength);
17     bool append(void* key,int klength,void* data,int dlength);
18     bool get(void* key,int klength,MCFetchedData*& fetchedData);
19     bool gets(void** keys,int* keysLength,int count);
20     bool fetchNext(MCFetchedData*& fetchedData,bool* isEof = 0);
21     bool remove(void* key,int klength);
22     bool exists(void* key,int klength,bool* ok = NULL);
23     bool updateWithCAS(void* key,int klength,void* data,int dlength);
24     bool updateWithAtomicIncrement(void* key,int klength,int step,uint64& value,int* defaultValue = 0);
25     bool updateWithAtomicDecrement(void* key,int klength,int step,uint64& value,int* defaultValue = 0);
26     bool clean();
27 
28 private:
29     memcached_st*    _mc;
30     memcached_return _mr;
31     uint64_t _cas;
32     bool _initialized;
33     bool _supportCAS;
34 };

  3.
仍旧在Mingw32碰到下,编写makefile文件,生成新的动态库,该库将重视libmemcached库。见如下makefile:

all:
      g++ -I/usr/local/include
-I”/home/Administrator/MemcachedClientWrapper”
../MemcachedClientWrapperImpl.cpp -o ./MemcachedClientWrapperImpl.o -O3
-w -c -fmessage-length=0 -MMD -MP -MF”MemcachedClientWrapperImpl.d”
-MT”MemcachedClientWrapperImpl.d”

      g++ -L/usr/local/bin -shared -o
“./MemcachedClientWrapper.dll” ./MemcachedClientWrapperImpl.o
-lmemcached-8

     见以下表达:

     1)
MemcachedClientWrapperImpl.cpp文件中饱含接口的贯彻部分已经七个导出C函数的落到实处。

     2)
/usr/local/include:
中蕴藏libmemcached的相关头文件,是在libmemcached的make
install中copy过去的。

     3)
/home/Administrator/MemcachedClientWrapper:
该目录包括该Wrapper工程头文件所在目录。

     4) /usr/local/bin:
该目录蕴含libmemcached生成的dll文件。

     5) libmemcached-8.dll:
当前版本libmemcached生成的动态库。

     6) MemcachedClientWrapper.dll:
为最终身成的动态库。

  4.
在施行完步骤3之后,我们将获取七个dll,贰个是原始的libmemcached.dll,另四个则为大家封装后的MemcacheClientWrapper.dll。在此之后,大家的VC程序将只是面临封装后的动态库及其导出接口,而libmemcached中的导出音信已经被很好的卷入在Wrapper的当中了。大家前几天内需做的便是在我们的工程少将纯虚接口所在的头文件包涵进大家的工程中,然后在概念七个C函数指针,其函数签字和Wrapper中程导弹出的多个C函数保持一致,见如下代码:

  typedef MemcachedClientWrapper\
(*createWrapper)();      typedef void
(*releaseWrapper)(MemcachedClientWrapper*);*

  5.
末段我们须要做的是在大家的测验用例中使用该Wrapper导出的纯虚接口来操作libmemcached,以便和Memcached服务器进行通信和数目存款和储蓄,见如下用例代码:

 1 #include <MemcachedClientWrapper.h>
 2 
 3 int main()
 4 {
 5     MemcachedClientWrapper* wrapper = createMCWrapper();
 6     void* key = malloc(20);
 7     void* data = malloc(20);
 8     memcpy(key,"helloworld",10);
 9     memcpy(data,"i love you, my baby.",20);
10     assert(wrapper->add(key,10,data,20));
11     free(key);
12     free(data);
13     releaseMCWrapper(wrapper);
14     printf("All Over.\n");
15     return 0;
16 }

三、原理深入分析:

  该本领有些近乎于Windows中的COM技巧,通过编写翻译器生成的C++设想表,以高达这种跨编译器的二进制接口格局。该本事有以下多少个注意事项:

  1.
定义纯虚接口,该接口中不能够定义任何成员变量,不然在编写翻译器之间不能完毕二进制包容。

  2.
概念C接口情势的工厂方法createMCClient(),用于成立实际的子类,那样使用者便足以经过动态链接的办法加载该库,如Windows中LoadLibrary和Linux中的dlopen。

  3.
定义C接口的对象指针释放方法releaseMCClient(),在接口中一度将接口的析构方法定义为protected类型,因而调用者不可能直接delete该指针,而是必需通过该接口指针导出的release()方法或然该C导出函数来刑释解教,固然两岸都足以高达释放财富的目标,可是大家在试行中依旧推荐应用该C导出函数,以便保险使用办法的一致性和前途的扩张性。

  4. 在概念纯虚接口所在的头文件中并非富含别的和兑现细节相关的音信。

  显而易见,一切都以那样的光明,小编对本人的设计也是至极自信,以至有一丢丢的得意,因为笔者一度采取该措施产生了重重库在C++
Builder和VC++之间的动员搬迁,从而到达这种跨编写翻译器的效率。能够虚构,就连极为错综相连的webkit也被作者在短短的一日内成功迁移,libmemcached应该是不言而喻的。不过谜底却是暴虐的,我的VC测量检验用例不可能符合规律调用该纯虚接口,每一遍都会报出和寄放器相关的荒谬。这让自家立即想到了事先代码中留存的标题,一定是自身在证明纯虚接口时,没有为每一种接口函数显明宣称调用标准,而gcc和VC++的缺省调用标准恰恰又是见仁见智的(VC++缺省为cdcel)。想到这里,笔者立刻做出修改,将具有接口函数的调用标准都内定为cdcel,然后用gcc重新编写翻译该Wrapper库。结果什么呢?请看下篇。

相关文章