科技报道|exe调用DLL的方式( 二 )


而使用_declspec(dllimport)却不是必须的 , 但是建议这么做 。 因为如果不用_declspec(dllimport)来说明该函数是从dll导入的 , 那么编译器就不知道这个函数到底在哪里 , 生成的exe里会有一个callXX的指令 , 这个XX是一个常数地址 , XX地址处是一个jmpdwordptr[XXXX]的指令 , 跳转到该函数的函数体处 , 显然这样就无缘无故多了一次中间的跳转 。 如果使用了_declspec(dllimport)来说明 , 那么就直接产生calldwordptr[XXX] , 这样就不会有多余的跳转了 。
2|3__stdcall带来的影响这是一种函数的调用方式 。 默认情况下VC使用的是__cdecl的函数调用方式 , 如果产生的dll只会给C/C++程序使用 , 那么就没必要定义为__stdcall调用方式 , 如果要给Win32汇编使用(或者其他的__stdcall调用方式的程序) , 那么就可以使用__stdcall 。 这个可能不是很重要 , 因为可以自己在调用函数的时候设置函数调用的规则 。 像VC就可以设置函数的调用方式 , 所以可以方便的使用win32汇编产生的dll 。 不过__stdcall这调用约定会Name-Mangling , 所以我觉得用VC默认的调用约定简便些 。 但是 , 如果既要__stdcall调用约定 , 又要函数名不给修饰 , 那可以使用*.def文件 , 或者在代码里#pragma的方式给函数提供别名(这种方式需要知道修饰后的函数名是什么) 。
举例:
·extern“C”__declspec(dllexport)bool__stdcallcswuyg();·extern“C”__declspec(dllimport)bool__stdcallcswuyg();·#pragmacomment(linker,"/export:cswuyg=_cswuyg@0")3|0编写测试dll代码项目结构:
#includeusingnamespacestd;extern"C"{_declspec(dllexport)voidprintN(intn){//printf("%dn",n);cout<<n<<endl;}}voidprintM(intm){cout<<m<<endl;}#pragmacomment(linker,"/export:getNresult=?getNresult@@YAHXZ")intgetNresult(){//printf("%dn",n);return123;}def代码:
LIBRARYDLLTESTEXPORTSprintM项目属性中将配置类型改为dll:
这里涉及一个问题 , 原始函数符号怎么找到的 , 方法是先用_declspec(dllexport)方式导出 , 然后编译后利用CFF即可看到原始函数符号 。
4|0编写exe调用dll项目结构:
#includeusingnamespacestd;#pragmacomment(lib,"C:projectdlltestDebugdlltest.lib")extern"C"__declspec(dllimport)voidprintN(int);intgetNresult();voidprintM(int);intmain(){printN(123);printM(12);cout<<getNresult()<<endl;return0;}在#pragma中更改为自己的lib路径 , printN与extern"C"__declspec(dllimport)形式导入 , getNresult和printM是c++格式的 , 应该使用__declspec(dllimport)导入 , 不过导入函数的情况下可以省略不写 , 引用外部变量则不能省略 。
执行结果:
代码:
#include#includeusingnamespacestd;intmain(){HINSTANCEh=LoadLibrary(L"C:projectdlltestDebugdlltest.dll");if(h==NULL){cout<<"dll加载失败!"<<endl;}else{void*func=GetProcAddress(h,"printN");if(func!=NULL){((void(*)(int))func)(2);}else{cout<<"未找到相关函数!"<<endl;}}return0;}需要注意将项目的字符集改为Unicode: