本来看hl2源码的目的是想学习他是如何使用IO Completion Port来实现网络通信的。但只是找到了一个文件ThreadedTCPSocket文件。源文件里使用了IO Completion Port方式封装通信类。但我在其他地方实在找不着调用此类的直接应用。所以,我就想从头开始看源码也许能找到使用此类的模块。误打误撞,将之前没看懂的部分弄明白了。将它写出来与大家共享。
我们知道主函数在launcher_main模块内。而launcher_main实际调用的是launcher.dll里输出的LauncherMain函数来完成程序初始化操作。
HINSTANCE launcher = LoadLibrary("launcher.dll");
...
LauncherMain_t main = (LauncherMain_t)GetProcAddress( launcher, "LauncherMain" );
return main( hInstance, hPrevInstance, lpCmdLine, nCmdShow );
上面列出的就是在launcher_main模块里main.cpp的代码。接下来我们再去看anucher模块里看看LauncherMain函数都做了些什么。
在看这部分的源码之前,先说明一下。游戏的功能分散在不同的模块里,均以动态链接库的形式存在。有几个比较重要的先介绍一下。
filesystem_stdio.dll
materialsystem.dll
engine.dll
vgui2.dll
vguimatsurface.dll
具体各实现什么功能我也不清楚。先不管这么多,知道他们都很重要就行了。
LauncherMain函数前面部分也没什么。有些还真不明白,也先跳过。到了while循环这,现在注意了有个很重要的函数要引起我们的注意。那就是,LoadAppSystems。LOAD很明显就是装载东西,但具体装载什么呢?让我们进入函数内看看。进入函数内我们看到了这么一段:
// Start up the file system
FileSystem_LoadFileSystemModule();
if ( !FileSystem_Init() )
return false;
这是此函数的第一部分。从注释中可以了解这些语句的作用是启动文件系统。二话不说,先看去看FileSystem_LoadFileSystemModule()函数。此函数就在此工程的filesystem.cpp文件里。
char *sFileSystemModuleName = "filesystem_stdio.dll";
从第一句申明可以看出,是要装载filesystem_stdio.dll文件。
g_pFileSystemModule = Sys_LoadModule(sFileSystemModuleName);
接着调用Sys_LoadModule装载此动态链接库,并获取其句柄。
g_FileSystemFactory = Sys_GetFactory(g_pFileSystemModule);
接着调用Sys_GetFactory。从字面上解释是从此动态链接库内获得什么工厂。其实是得到一个函数地址,供之后的调用。
g_pFileSystem = (IFileSystem *)g_FileSystemFactory(FILESYSTEM_INTERFACE_VERSION, NULL);
最后利用刚才得到的函数地址,调用此输出函数。并返回一个值赋给g_pFileSystem。然后程序退出。g_pFileSystem是个全局变量就在此文件的开始处申明,是个指向IFileSystem的指针。从这些代码中可以得出FileSystem_LoadFileSystemModule函数的作用就是为了获得动态链接库内地某个东西,并将其保留在本进程的变量里。
这里有很多类型都不明白是些什么。例如,IFileSyste;还有那些函数,Sys_LoadModule、Sys_GetFactory等。很乱,有种理不清头绪的感觉。不管,先看函数再说。刚才列出的两个函数都存在于interface.cpp文件里。Sys_LoadModule函数只要去除那些linux下的代码其实很简单就是装载动态链接库并返回句柄。与我们刚才猜想的一样。好,跳过这个函数再看Sys_GetFactory。
return reinterpret_cast(GetProcAddress(hDLL, CREATEINTERFACE_PROCNAME));
这个函数也很简单,有用的就两句。重要的就是上面这句。GetProcAddress,我知道这个函数不就是获取dll内的函数地址嘛。再来看看具体是哪个函数,CREATEINTERFACE_PROCNAME定义为CreateInterface。这个定义是在interface.h文件里找到的。也就是说所谓的工厂就是指的这个函数CreateInterface。这时我就有点明白了,动态链接库藉由此函数创建什么接口提供给调用此动态链接库的使用方。有点眉目了,再继续查找CreateInterface函数。在interface.h里我们看到了这么一句:
DLL_EXPORT void* CreateInterface(const char *pName, int *pReturnCode);
说明此dll确实输出了CreateInterface。果然,在interface.cpp里我们找到了此函数的定义。也不复杂,就是利用传入的字符参数与其他变量做比较。如果存在相同的,则返回一个函数;否则置错误状态值,并返回空指针。又有点糊涂了,不是返回变量嘛,怎么还是函数?而且函数内做比较用的变量是个类的静态变量。在interface.cpp文件的前面我们找到了它,InterfaceReg *InterfaceReg::s_pInterfaceRegs = NULL;。没办法,再去看InterfaceReg类的申明。也没什么,在CreateInterface函数里几乎所有的类成员变量都有所涉及。而且这些变量都很好理解。InterfaceReg *m_pNext,从这个成员变量可以得知此类的实例会形成一个链表。谁来创建一个类似这样的链表呢?构造函数里好像有所体现。真的是这样,在类事例创建之时,实例会将自己加入到此链表内。除此之外我们好像得不出其他什么信息了。但还是有疑问,谁来创建这个实例。不可能在动态链接库之外,但在模块内我们又没发现其他的信息。
不会吧,又卡壳了!唉,没办法谁让我们看这些没有文档的源码呢。关了这个工程,气死我了。我又打开了Filesystem_stdio.dsp工程。刚才好像是在调用这个dll,说不定能找到什么有用的信息。为什么这个工程里也有interface.cpp和interface.h?打开这两个文件看看,一模一样。明白了,通用的文件。说不定猫腻还在这两个文件里。再仔细找找呗。.cpp没什么,那就去看.h。里面有什么看仔细了,两个类的申明,还有一些函数申明,中间有一大串的#define(看不懂),一个enum,然后是extern。没了。不会吧?还是不能偷懒,看那些#define。苦命啊!有门,第一句就是一个静态InterfaceReg类型的定义。生成了一个类实例不就是创建了链表嘛。接下来三个大同小异只是构造方式不一样而已。再依次在工程内查找这四个#define。EXPOSE_INTERFACE_FN没有,EXPOSE_INTERFACE也没有。靠!骗人的嘛,还不出现。查EXPOSE_SINGLE_INTERFACE_GLOBALVAR。嘿嘿,有了。在filesystem_stdio.cpp文件的最后两行。
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IFileSystem, FILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio );
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CFileSystem_Stdio, IBaseFileSystem, BASEFILESYSTEM_INTERFACE_VERSION, g_FileSystem_Stdio );
CFileSystem_Stdio、IFileSystem和FILESYSTEM_INTERFACE_VERSION都明白,前两个是类,后一个是字串。g_FileSystem_Stdio,这看起来像个全局变量。嘿,小样儿!就在前面一句申明了此变量。
static CFileSystem_Stdio g_FileSystem_Stdio;
哈哈!我知道了在dll初始化时就会生成一个InterfaceReg类实例。但EXPOSE_SINGLE_INTERFACE_GLOBALVAR这个具体作了些什么还是不明白。好继续分解。
#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) static void* __Create##className##interfaceName##_interface() {return (interfaceName *)&globalVarName;} static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName);
看上去好像很复杂。没关系就将上面的实际的例子替代一下。
static void* __CreateCFileSystem_StdioIFileSystem_interface()
{
return (IFileSystem*)&g_FileSystem_Stdio;
}
static InterfaceReg __g_CreateCFileSystem_StdioIFileSystem_reg(__CreateCFileSystem_StdioIFileSystem_interface, FILESYSTEM_INTERFACE_VERSION);
这样看上去清除多了。一个函数定义,一个InterfaceReg类实例的创建。函数实际上返回的是个全局变量,类型为CFileSystem_Stdio。怎么强制转换为IFileSystem?哦,看了CFileSystem_Stdio就知道了。IFileSystem是它的虚基类。是可以这么做。再来看类实例的创建。去看他的构造函数。第一个参数是个函数,第二个是个字串。函数将放在此类实例的成员变量m_CreateFn里,字串赋给m_pName。传给构造函数的第一个参数就是利用EXPOSE_SINGLE_INTERFACE_GLOBALVAR一块生成的一个函数。
我明白了。构造InterfaceReg的FILESYSTEM_INTERFACE_VERSION与下面这句的g_pFileSystem = (IFileSystem *)g_FileSystemFactory(FILESYSTEM_INTERFACE_VERSION, NULL)内使用的FILESYSTEM_INTERFACE_VERSION,是同一个字串。其实就是将动态链接库内的g_FileSystem_Stdio全局变量地址赋给调用方的全局变量g_pFileSystem。这样一来,调用方就可以使用动态链接库内类实例。
看下图的图例解释。
这确实是hl2输出接口的方式。在其他模块里我们也找到了interface.cpp和interface.h两个文件。说明所有的模块都是通过这种方式输出接口的。
上面我们分解的是hl2里动态链接库输出接口的方式。CreateInterface应该是恒久不变的。利用此接口我们能存取动态链接库内的类的实例。而且这种方式还能实现版本控制。保证能够调用到正确的类实例。程序在不断发展修正,必然有更新的版本出来。如果在新版本的动态链接库内不存在我们需要的接口,程序也不会调用到错误的接口而是转向到其他正确的动态链接库内去查找需要的接口。这样保证了程序不会出现异常,而是以当前能够运行的版本运行程序。