【学习笔记】调用dll文件的方式及注意事项
2013-03-26 22:17
288 查看
最初调用DLL文件时,我曾犯过几个错误。下面记录几种调用DLL文件的方法以及容易出错的地方。
先来看看dll代码,仅含一个cpp文件,工程使用了多字节字符集:
根据导出函数原型,可定义如下函数指针类型:
一、静态调用
静态调用在编译时需要lib文件,并作函数声明。
首先在调用前添加一行代码:#pragma comment(lib,"DllTest.lib")
其次声明导入的函数:__declspec(dllimport) void SayHello(string);// :__declspec(dllimport)可改为extern,但使用__declspec(dllimport)声明会更加高效
(1)不一定要用#pragma加载lib文件,也可以在工程属性-链接器中的附加库目录中添加lib文件(VS2010)。
(2)关于函数导入,之间添加一个输出函数的头文件会比较方便。
代码:
View Code
需要特别留心的是GetProcAddress函数的使用:
(1)GetProcAddress函数返回FARPROC类型的值,需要进行强制类型转换为对应函数指针的类型。
(2)GetProcAddress函数原型:
lpProcName是接收的是一个函数名、变量名、或者是一个序号。在说明此参数前先使用dumpbin.exe(可在vc或vs的bin目录下找到)查看一下dll文件的输出函数等信息,在命令行中输入dumpbin.exe -exports [dll文件目录] 即显示下列信息:
①前面动态调用dll文件的代码中我使用的序号(ordinal)来指定DllTest中SayHello输出函数的位置,从信息中可以看出SayHello函数的序号为1。因为lpProcName是LPCSTR类型的,因此可以使用MAKEINTRESOURCE宏来转换(推荐)或者将序号直接强制类型转换为LPCTSTR类型。
②若想使用函数名来获取输出函数位置,请特别注意以下写法是错误的:
由dumpbin.exe输出的信息可知,正确的函数名应该为"?SayHello@@YAXV?$basic_string@DU?$char_traits@D@std@@V
?$allocator@D@2@@std@@@Z",因此正确的代码应该为:
显然很麻烦,这是由于C++支持函数重载,因此对不同的函数名,编译器会采用不同的规则进行函数名转换(貌似有个专用名叫函数名粉碎)。若希望行A中的代码正确,则需修改dll代码中的DLL_TEST宏:
这样一来,就可以使用行A代码获取函数地址了。这种方法的缺点是使用extern "C"定义后,就不能编写类了。此时使用dumpbin.exe查看,输出表信息如下:
一种较好的方法就是定义一个后缀名为def的模块定义文件,用此来规定函数名的命名方式,这样一来函数名的问题就解决了。而且,用模块定义文件定义后,dll文件将可以在delphi(__stdcall函数调用约定)等其他语言中使用了。对于def的详细使用方法可以到网上查阅一下。
曾经写过一个用于实现API隐藏功能的dll文件,即批量、自动化地将C++代码中的重要的API函数全部转换为使用LoadLibrary函数获取函数地址间接调用,以求可执行文件在被用OD反汇编出来后,难以发现API函数(如MessageBox)调用的位置。对于此功能的实现,我曾经用过静态调用和动态调用两种方式,如果是静态调用,只需要在预编译头中插入#pragma链接lib文件,然后将API函数替换为已经写好的函数即可。但如果是使用动态调用的方式,因为每一次使用的时候,动态调用代码都比较长,因此可以考虑将动态调用函数的代码封装成类。跑题了,哈哈。
作者:SunboyL /article/7077600.html
先来看看dll代码,仅含一个cpp文件,工程使用了多字节字符集:
#define DLL_TEST _declspec(dllexport) #include <Windows.h> #include <string> DLL_TEST void SayHello(std::string str) {// dll导出函数,弹出Hello字符框 std::string show = "Hello," + str; MessageBox(NULL,(LPCTSTR)str.c_str(),"HI",MB_OK); }
根据导出函数原型,可定义如下函数指针类型:
typedef void (*ShowMsg)(string);// 后文默认已经定义ShowMsg类型
一、静态调用
静态调用在编译时需要lib文件,并作函数声明。
首先在调用前添加一行代码:#pragma comment(lib,"DllTest.lib")
其次声明导入的函数:__declspec(dllimport) void SayHello(string);// :__declspec(dllimport)可改为extern,但使用__declspec(dllimport)声明会更加高效
(1)不一定要用#pragma加载lib文件,也可以在工程属性-链接器中的附加库目录中添加lib文件(VS2010)。
(2)关于函数导入,之间添加一个输出函数的头文件会比较方便。
代码:
View Code
#include <iostream> #include <string> #include <Windows.h> using namespace std; //#pragma comment(lib,"DllTest.lib") //__declspec(dllimport) void SayHello(string); typedef void (*ShowMsg)(string); int main() {// 动态调用dll HMODULE hwnd; ShowMsg show; if((hwnd = LoadLibrary("DllTest.dll")) == NULL) {// 加载文件失败则退出程序,可以用GetLastError()函数获取错误信息 MessageBox(NULL,"Failed to load library.","Error",MB_OK); return 1; } if((show = (ShowMsg)GetProcAddress(hwnd,MAKEINTRESOURCE(1))) == NULL) {// 参数MAKEINTRESOURCE(1)可以改为(LPCSTR)1 或者直接输入函数名 MessageBox(NULL,"Failed to read function.","Error",MB_OK); FreeLibrary(hwnd); return 1; } show("SunboyL");// 利用函数指针调用dll中的导出函数 FreeLibrary(hwnd);// 释放动态链接库 return 0; }
需要特别留心的是GetProcAddress函数的使用:
(1)GetProcAddress函数返回FARPROC类型的值,需要进行强制类型转换为对应函数指针的类型。
(2)GetProcAddress函数原型:
FARPROC WINAPI GetProcAddress( __in HMODULE hModule, __in LPCSTR lpProcName );
lpProcName是接收的是一个函数名、变量名、或者是一个序号。在说明此参数前先使用dumpbin.exe(可在vc或vs的bin目录下找到)查看一下dll文件的输出函数等信息,在命令行中输入dumpbin.exe -exports [dll文件目录] 即显示下列信息:
File Type: DLL Section contains the following exports for DllTest.dll 00000000 characteristics 51516C33 time date stamp Tue Mar 26 17:36:51 2013 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 0001103C ?SayHello@@YAXV?$basic_string@DU?$char_traits@D@std@@V ?$allocator@D@2@@std@@@Z Summary 1000 .data 1000 .idata 3000 .rdata 1000 .reloc 1000 .rsrc 8000 .text 10000 .textbss
①前面动态调用dll文件的代码中我使用的序号(ordinal)来指定DllTest中SayHello输出函数的位置,从信息中可以看出SayHello函数的序号为1。因为lpProcName是LPCSTR类型的,因此可以使用MAKEINTRESOURCE宏来转换(推荐)或者将序号直接强制类型转换为LPCTSTR类型。
②若想使用函数名来获取输出函数位置,请特别注意以下写法是错误的:
show = (ShowMsg)GetProcAddress(hwnd,"SayHello");// 行A
由dumpbin.exe输出的信息可知,正确的函数名应该为"?SayHello@@YAXV?$basic_string@DU?$char_traits@D@std@@V
?$allocator@D@2@@std@@@Z",因此正确的代码应该为:
show = (ShowMsg)GetProcAddress(hwnd,"?SayHello@@YAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z");// 行B
显然很麻烦,这是由于C++支持函数重载,因此对不同的函数名,编译器会采用不同的规则进行函数名转换(貌似有个专用名叫函数名粉碎)。若希望行A中的代码正确,则需修改dll代码中的DLL_TEST宏:
#define DLL_TEST _declspec(dllexport) ==>> #define DLL_TEST extern "C" _declspec(dllexport)
这样一来,就可以使用行A代码获取函数地址了。这种方法的缺点是使用extern "C"定义后,就不能编写类了。此时使用dumpbin.exe查看,输出表信息如下:
ordinal hint RVA name 1 0 00011348 SayHello = @ILT+835(_SayHello)
一种较好的方法就是定义一个后缀名为def的模块定义文件,用此来规定函数名的命名方式,这样一来函数名的问题就解决了。而且,用模块定义文件定义后,dll文件将可以在delphi(__stdcall函数调用约定)等其他语言中使用了。对于def的详细使用方法可以到网上查阅一下。
曾经写过一个用于实现API隐藏功能的dll文件,即批量、自动化地将C++代码中的重要的API函数全部转换为使用LoadLibrary函数获取函数地址间接调用,以求可执行文件在被用OD反汇编出来后,难以发现API函数(如MessageBox)调用的位置。对于此功能的实现,我曾经用过静态调用和动态调用两种方式,如果是静态调用,只需要在预编译头中插入#pragma链接lib文件,然后将API函数替换为已经写好的函数即可。但如果是使用动态调用的方式,因为每一次使用的时候,动态调用代码都比较长,因此可以考虑将动态调用函数的代码封装成类。跑题了,哈哈。
作者:SunboyL /article/7077600.html
相关文章推荐
- (学习笔记)C++编写dll C#调用注意事项
- 如何寻找使用案例及其注意事项,学习笔记
- Java学习笔记(77)-----------注释注意事项
- JAVA程序员养成计划之JVM学习笔记(0)-一些注意事项
- 黑马程序员之---C学习笔记之printf与scanf使用及注意事项
- 韩顺平 javascript教学视频_学习笔记8_js系统函数_js函数调用方式
- SQL中like关键字结合SqlParameter使用时的注意事项(学习笔记)
- JavaScript学习笔记-03函数调用方式
- C++ Primer学习笔记:引用的使用注意事项
- Google Cloud Speech API 调用注意事项及调用方式__.Net版2
- Cocos2dx学习笔记35 CCArray使用注意事项
- VLAN 注意事项、学习要点及其总结(笔记)
- 【Java学习笔记之八】JavaBean中布尔类型使用注意事项
- 【JavaEE】javaEE学习笔记之分页实践操作笔记重点和注意事项
- V-rep学习笔记:外部函数调用方式
- .NET工程中以 C 和 C++ 两种方式编译时,函数调用注意事项
- 【学习笔记】C语言:做题注意事项(仅做题)
- Delphi 2010学习笔记(16)---流程控制的注意事项---2011-01-21
- 【java基础知识(学习笔记)】--方法的重载注意事项
- JS学习笔记2015-4-15(第二天)——属性操作的注意事项&中括号在JS中的使用