云游戏GamingAnyWhere的源码阅读已经有段时间了,由于它是第一款开源的云游戏产品,因此无论是在不通种类游戏的兼容性上,还是实际投入生产(比如:多个用户连接同一台服务器上的游戏,接收到的画面和操作如何互不影响),都有很大的不足,可以说当前版本的GA还仅仅是一个样例式的应用,相比投入生产环境还面临很大的提升空间。
这段时间完成的工作:
1> 为了提升性能,利用Nvidia SDK进行源码改造;完成程度:可以支持GA官网提供的支持D9方式启动的游戏通过 Nvidia SDK进行捕捉,客户端可以连接游戏。
正在进行的工作:
1> 以《*飞车》为例生成支持x64游戏以及采用DirectX11方式启动方式的代码改造。
2> GA服务端构建守护进程,同时客户端配合修改以完成一个客户端连接过来,直接启动游戏、建立连接、并进行数据传送。(注:当前的思路是这样,不排除中途修改)
尚未开展的工作:
1> 由使用游戏拓展到使用任何应用软件,以前的实现方式是走RDP,经过Windows系统,这样的实现方式有一个极大的便利,就是用户会话以及权限的控制;而通过GA改造后就完全脱离了系统级别的约束,通过捕捉服务器端应用程序的画面、经过压缩、传递给客户端,同样捕捉客户端的操作指定作用于服务器端完成整个控制;因此预计这个工作是GA改造中最为困难的工作,如何对用户进行身份识别以及权限控制?当前我们只知道,rtsp协议中对每一个连接都会有一个session,我们的想法是将这个session和系统的session进行映射以完成权限的控制。(当然只是猜想,不知道是否可行。)
上面啰嗦的就是这段时间以来在GA上进行的工作,由于我对C++了解的相对比较少,因此在源码的阅读过程中感觉很吃力,其中涉及到了很多的第三方开源库,少不了和dll打交道,包括在编译之初,因此想逐渐的积累对这方面知识的掌握,这篇文章不涉及GA的原理以及代码改造,只是对我自己欠缺的地方补充记录,当然之所以还没有写关于GA深层的东西,是因为现在我对它还没有宏观的把握,很多地方还没有吃透,因此关于GA的文章会在我工作推进的过程中逐渐补充的,下面就来看看静态链接库与动态链接库相关的内容吧。
"库",顾名思义可以理解为是存放第三方函数的地方,静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则lib中的指令都被直接包含在最终生成的exe文件中了,该文件比较大,但是可以独立运行在各种平台上;若使用动态链接库,即dll,则该dll不必被包含在最终的exe文件中,因此该exe文件相对较小,exe文件执行时可以动态地引用与卸载相关的dll文件,但是该exe必须和dll打包发送,不然在exe运行时会提示缺少相应的dll文件。静态链接库和动态链接库的另一个区别在于静态链接库中不能再包含其它的动态链接库或静态链接库,而在动态链接库中还可以再包含其它的动态或静态链接库。
下面就静态链接库与动态链接库的创建与使用给出详细的示例,我们将新建四个项目:
1> StaticLib 用于创建静态链接库
2> StaticLibTest 用于使用静态链接库
3> DllLib 用于创建动态链接库
4> UseDllLib 用于使用动态链接库
一、创建静态链接库
说明:如果使用静态链接库必须在项目中引用静态链接库的 .h 和 .lib 文件,因此我们创建静态链接库的最终目的就是编译出 .h 和 .lib 文件供我们使用就可以了。
新建win32的项目,选择静态库,完成:
新建头文件 houqd.h 添加如下代码,代码具体含义请见注释:
/* extern "C" 使得在C++中使用C编译,声明该函数使用C编译方式 cpp文件在编译为OBJ文件时要对函数进行重命名,C语言会把函数名name重命名为:_name的形式,而C++会重新命名为_name@@decoration extern "C"就限定了程序使用C语言的命名方式 例如: int main() { myfunc(); ... return 0 ; } 编译器生成像:call myfunc这样的代码 连接器生成像:call 0x4000000 这样,0x4000000代表myfunc函数的地址,如果myfunc在Dll中,则链接器无法直接 得到myfunc函数的地址,而利用_declspec(dllexport)则可以直接告诉连接器直接调用myfunc */ // 递归求和函数 extern "C" _declspec(dllexport) int RecursionSum(int num);
新建houqd.cpp文件,引用houqd.h头文件,实现函数即可,如下所示:
#include "houqd.h" int RecursionSum(int sum) { if(sum == 1) return 1 ; else return sum += RecursionSum(sum-1); }
编译成功后,在项目的Debug目录下即可生成我们需要的lib文件,注意这时不要启动,因为程序无法执行,它会报出:Unable to start program的错误,因为我们目的是创建库文件,并没有main入口。
既然不能启动那么如何调试静态链接库中我们自己写的函数呢?可以在该项目下Add -> New project -> 这次选择win32 console applicaion , 引用静态链接库,加断点F10进行调试。
二、使用静态链接库
声明:静态链接库的使用很简单,最关键的地方就是正确引用它的 .h 文件和 .lib文件,而正确引用需要注意两点:
1> 在项目C/C++ 下 Additional Include Directorires中添加.h文件所在路径,在Linker 下Additional Library Directories中添加 .lib 文件所在路径。
2> 在cpp文件中include所需.h文件,以及#pragma comment将lib文件链接到该文件中
为了模拟正式的应用,我们将StaticLib中生成的 .h 和 .lib 文件拷出来,新建include 和 lib 文件将,分别对应放进去,然后新建Win32 Console Applicaion项目。
第一步:在项目中正确引用.h 和 .lib文件
第二步:在cpp文件中使用我们引用的静态lib库中的函数,以完成我们所需的功能。
// StaticLibTest.cpp : Defines the entry point for the console application. // #include <stdlib.h> #include "stdafx.h" #include "houqd.h" // 引用外部的lib库 #pragma comment(lib , "StaticLib.lib") // #pragma comment(lib , "sample.lib")用来将一个库文件链接到目标文件中 #pragma message("Hello houqd ..") int _tmain(int argc, _TCHAR* argv[]) { int fin = 0 ; fin = RecursionSum(100); printf("1-100的累加值为:%d.\n" , fin); system("pause"); // system函数在stdlib.h中 return 0; }
编译运行项目,可以看到正确输出。
三、创建动态链接库
声明:动态链接库项目中的.h 以及 .cpp文件都准寻特定的格式,我们创建动态链接库最终的目的是要产生 .h , .lib 以及dll文件。创建动态链接库和创建静态链接库项目都是win32 project项目,只是在下一步选择,DLL,选中Exports symbols, 如下:
然后项目会生成相应的.h 以及 .cpp 文件,注意:自动生成的文件中均提供了参考示例(还是蛮人性化的哈),分别对应着:导出类、导出变量、导出函数:
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the DLLLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // DLLLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef DLLLIB_EXPORTS #define DLLLIB_API __declspec(dllexport) #else #define DLLLIB_API __declspec(dllimport) #endif // This class is exported from the DllLib.dll // 导出类的声明 class DLLLIB_API CDllLib { public: CDllLib(void); // TODO: add your methods here. }; // 导出变量的声明 extern DLLLIB_API int nDllLib; // 导出函数的声明 DLLLIB_API int fnDllLib(void); // 自定义求和函数 DLLLIB_API int dll_sum(int a , int b);
其中,DLLLIB_API int dll_sum( int a , int b ); 即为我们自定义的导出函数,同样参考cpp的实现:
// DllLib.cpp : Defines the exported functions for the DLL application. // #include "stdafx.h" #include "DllLib.h" // This is an example of an exported variable DLLLIB_API int nDllLib=0; // This is an example of an exported function. DLLLIB_API int fnDllLib(void) { return 42; } // 自定义函数实现 DLLLIB_API int dll_sum(int a , int b) { return a + b ; } // This is the constructor of a class that has been exported. // see DllLib.h for the class definition CDllLib::CDllLib() { return; }
到这,动态链接库的实现已经基本完成,编译通过,对应目录生成.lib 和 .dll文件 , 其实关于动态链接库到这里还没有完美地完成,因为我们不知道我们的函数是否真正存在于dll中,我们的函数名真的就如我们定义的那样吗?(这里是dll_sum),下面就要说到一个非常中要的工具,是VS2010自带的工具,即:dumpbin , 它可以帮助我们查看一个dll文件中究竟存在哪些可用函数,以及那些函数名到底是怎样的。
启动Virtual Studio Command Prompt (2010) , 进入到生成的dll文件所在目录,运行命令:DUMPBIN /EXPORTS DllLib.dll
什么?我们的函数名怎么成了 ?dll_sum@@YAHHH@Z 这种形式?难道我们要使用这种形式调用?能不能编程我们熟悉的dll_sum的形式啊?如何能将dll导出的函数名规范化啊?
这里方法有两种,一种是运用 extern "C" 修饰 dll_sum;二是运用模块定义文件.def,这里我们采用第二中方式:Add -> New item -> Module-Definition File(.def),内容如下:
LIBRARY DllLib EXPORTS dll_sum = ?dll_sum@@YAHHH@Z LIBRARY
保存,重新编译,重新使用DUMPBIN工具查看:
OK , 编程我们熟悉的模式了,好了,接下来我们就可以使用该动态链接库文件了。
四、使用动态链接库
声明:动态链接库的使用也遵循一定的规则,正确添加 .h 和 .lib 文件所在路径我就不说了,在使用上也有特殊之处,先上代码:
// UseDllLib.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <stdlib.h> #include <Windows.h> int main(int argc, _TCHAR* argv[]) { const char* dllName = "DllLib.dll"; const char* mySum = "dll_sum"; // 声明函数指针 int (*my_sum)(int , int); int a = 3 ; int b = 5 ; int c = 0 ; // 将dll load到exe中 HMODULE hDll = LoadLibrary(dllName); if(hDll != NULL){ // 获取dll_sum函数的指针 , 并将它赋给我们声明的函数指针 my_sum = (int (*)(int , int))GetProcAddress(hDll , mySum); if(my_sum == NULL){ printf("GetProcAddress execute error.\n"); system("pause"); return 1; }else{ // 真正调用 c = my_sum(a ,b); printf(" a + b = %d + %d = %d .\n" , a , b , c); } FreeLibrary(hDll); }else{ printf("LoadLibrary execute error. \n"); system("pause"); return 2 ; } system("pause"); return 0; }
代码中的要点说明:
1> 要包含头文件Windows.h,因为程序中用到了LoadLibrary , FreeLibrary , GetProcAddress等Win32 API函数。
2> my_sum 为函数指针的声明
3> 当程序不再使用dll时,应该调用FreeLibrary及时释放它占用的空间
4> 如果在const char* mySum底部出现红色波浪线提示,说明采用的字符集不匹配,需要修改项目属性Charater Set 为Not Set。
好了,上面就是今天整理的内容,在GA中大量使用了LoadLibrary、GetProcAddress等函数使用外部的dll,在安装游戏的过程中有的时候游戏启动时会保存缺少某dll文件,就是因为游戏程序在运行时会动态调用dll文件中的函数来实现某些功能,如果缺少的话就会报出这样的错误,今后关于所有的这一切就都理解了。下一篇打算将一下windows中的钩子函数,因为在理解GA的代码中也是很关键的一个地方,花了好长时间再得知,是在正在进行的工作第二条中发现的,对理解GA很有帮助。
相关参考链接:
1> VC++ 动态链接库(DLL)编程深入浅出:http://www.pconline.com.cn/pcedu/empolder/gj/vc/0509/698632.html
2> VS2010创建并使用DLL:http://www.cnblogs.com/laogao/archive/2012/12/07/2806528.html
只要每天自己有一点点进步,都是值得记录和珍惜的。