wolfet研究-1

wolfet就是Return To Castle Wolfenstein : Enemy Territory的简称。

wolfet是一款跨平台的游戏。为了支持这种跨平台的特性,wolfetOpenGL的接口进行了一番封装。

OpenGL中有这么一个函数:glVertex3f。与之相对应,et中定义了这么一个函数指针:qglVertex3f。在Linux平台下,src/unix/linux_qgl.h中,它的定义是这样的:

void ( APIENTRY * qglVertex3f )( GLfloat x, GLfloat y, GLfloat z );

而对APIENTRY的定义如下:

#ifndef APIENTRY

#define APIENTRY

#endif

也就是没有作用,只是为了未来扩展性做的预定义。

其他平台下的同名函数指针的样式也基本相同。然后在src/unix/linux_qgl .c中定义的函数qboolean QGL_Init( const char *dllname )

1,使用dlopen()函数打开OpenGL.so动态库

2,对于qglVertex3f这个函数指针,使用:

qglVertex3f = dllVertex3f = GPA( "glVertex3f" );

这句话将.so中的glVertex3f这个函数的指针赋给qglVertex3f。其中GPA是用来呼叫dlsym()执行这个动作的宏。

3,然后在代码中,就可以使用qglVertex3f来调用GL函数了。

之所以要使用函数指针这个间接方式,就是因为linuxwindowsmac平台上的步骤123都有些许差别,所以要用不同平台上不同版本的QGL_Init()函数将这些差别封装起来。

----------------------------------------
如果使用visudo studio打开etmpmulti-player)部分的项目,就会发现有8个项目。现在尝试解析一下:

1botlib:包含的基本上是botlib这个文件夹中的代码,生成的是botlib.lib库文件。函数基本以AAS_Bot开头。

2cgame:包含的是cgamegame文件夹中的代码,生成的是cgame_mp_x86.dll

3extractfuncs:包含的是extractfuncs文件夹中的代码,生成的是extractfuncs.exe,貌似作用是在一个.bat中被调用以处理gamebotai目录中的.c文件,生成xx_funcs.hxx_func_decs.h,其中导出了函数。

4game:主要包含了botaigame目录中的代码,生成qagame_mp_x86.dll

5renderer:主要包含了rendererft2FreeType2)、jpeg-6目录中的代码,生成库文件。既然名为渲染器,提供的自然也是和渲染相关的函数。

6Splines:主要包含了splines目录中的代码,生成Splines.lib文件。splines是曲线的意思,这个库也主要提供了和数学相关的曲线函数。

7ui:主要包含了ui目录的代码,生成ui_mp_x86.dll文件。

8wolf:主要包含了clientserverwin32curl四个目录的代码,生成et.exe,即主可执行文件。

这里面说到了13个文件夹,而目录bspcqcommon没有被提到。貌似这两个文件夹中的代码都是基础代码,应用很广泛,所以很难说属于哪个项目。

-------------------------------------
我发现一个很有趣的特性,生成的cgame_mp_x86.dllqagame_mp_x86.dllui_mp_x86.dll这三个文件都只有两个接口:

void dllEntry( int ( QDECL *syscallptr )( int arg,... ) )

int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 )

函数dllEntry()的作用是把系统函数输入到dll中以供其使用。例如src/client/cl_cgame.c中的函数void CL_InitCGame( void )中有这么一句:

cgvm = VM_Create( "cgame", CL_CgameSystemCalls, VMI_NATIVE );

其作用是:

1,一些预先处理

2,加载dll文件,这里是cgame_mp_x86.dll

3,名为vm_t的结构体的指针vm保存在src/qcommon/vm.c的全局变量vm_t vmTable[MAX_VM]中。

4,调用Sys_LoadDll()这个函数

4.1,调用dllEntry(),使cgame/cg_syscalls.c中定义的syscall这个static的函数指针等于src/client/cl_cgame.c中定义的函数CL_CgameSystemCalls()

4.2,把dll中的vmMain()这个函数的指针保存在vm中的叫做entryPoint的函数指针中。

来看看函数int CL_CgameSystemCalls( int *args )

1switch ( args[0] )

1.1,如果是CG_FS_READ,则FS_Read( VMA( 1 ), args[2], args[3] );

1.2,如果是CG_XXXX,则XXXX

1.3...........

来看看cgame/cg_syscalls.c中定义的void trap_FS_Read( void *buffer, int len, fileHandle_t f )

1syscall( CG_FS_READ, buffer, len, f );

也就是说,在上述的VM_Create()执行完后,呼叫trap_FS_Read()就等于呼叫CL_CgameSystemCalls,间接呼叫了FS_Read()

同时要注意,可以看到,FS_Read()中有三个参数,第一个是指针,长度为机器的位数长(例如32位机中就是32);第二个为int长度是32位;第三个fileHandle_t的定义是int,所以长度也是32位。这三个参数的长度在32位机器上是一致的,而在64位机器上就不一致了。但貌似wolfet是有64位版本的,而这个特性会不会导致参数错误,还需要继续研究。

同时,可以看到卡马克也充分考虑到了未来扩展性的问题。如果fileHandle_t需要扩充成含有多参数的结构体,只要改为:

struct fileHandle_s {//xxxxx};

typedef struct* fileHandle_s fileHandle_t;

也就是把int改为指针就可以了。

来看看cgame/cg_main.c中的int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 )

1,呼叫函数前有这么一个预处理:

#if __GNUC__ >= 4

#pragma GCC visibility push(default)

#endif

呼叫之后又有这么一个预处理:

#if __GNUC__ >= 4

#pragma GCC visibility pop

#endif

网上搜了一下,这里说是用来标识符号的可见性,没理解什么意思。

2switch ( command )

2.1,如果CG_INIT,则CG_Init( arg0, arg1, arg2, arg3 );

2.2,如果CG_SHUTDOWN,则......

2.3..............

那么在之前提到的VM_Create()里,步骤4.2vmMain()的指针放在了vm中。看一下entryPoint这个函数指针的样子:

int ( QDECL *entryPoint )( int callNum, ... );

并没有指示出具体有多少参数。

例如,在src/client/cl_cgame.cvoid CL_InitCGame( void )中,有这么一句:

VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum, clc.demoplaying );

就是调用了CG_Init()这个函数。

来看int QDECL VM_Call( vm_t *vm, int callnum, ... )

1,如果是linux平台

1.1,内部参量int args[16];

1.2,用va_xxx宏将后面省略的参数写入args

1.3,关键是这句:

r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15] );

2,如果是windows平台

2.1,关键是这句:

r = vm->entryPoint( ( &callnum )[0], ( &callnum )[1], ( &callnum )[2], ( &callnum )[3], ( &callnum )[4], ( &callnum )[5], ( &callnum )[6], ( &callnum )[7], ( &callnum )[8], ( &callnum )[9], ( &callnum )[10], ( &callnum )[11], ( &callnum )[12] );

当然,vmMain()中呼叫的函数的参数也都是与机器位长相同的。

--------------------------------------------------------

卡马克这些代码写的相当精彩,将C语言的函数指针特性的优点发挥地淋漓尽致。正如其名所指出的,cgame_xxx.dllqagame_xxx.dllui_xxx.dll就像三个虚拟主机,与wolf.exe通过dllEntryvmMain这两个标准接口才互相结合在一起。接口的简略性和抽象性都有助于函数的扩充和函数参数的修改。唯一的缺点是,接口的参数的限制增大了,只能在指针和int之间选择。但只要写代码时足够小心,这不会限制到功能,可谓利大于弊。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值