DLL基础
想写个程序,可以监控某个进程,然后对其进行某些操作…别怀疑我的动机,我是和谐社会的良民
然后第一步开始,DLL到底是什么?听到DLLl这词的次数绝对比我的年龄大(本人年方22,嘿嘿!),不过却从来没去研究过,所以一直觉得DLL很神秘。不过,箭在弦上,不得不去了解了。想找本书看的,结果搜罗了半个小时没发现,烦了,就开始看某人的blog,实现了后觉得豁然开朗啊。微软官网的帮助也不错。
原文写的很不错。写下我的感受。
【概念】
DLL是动态链接库的缩写,是一个包含有可使多个程序同时使用的数据和代码。使用促进代码的模块化、代码重用、内存的有效使用和减少所占用的磁盘空间。不过,由于使用DLL的程序需要实时的调用DLL中的数据或者代码,所以DLL必须和软件一起发放。
DLL是建立在客户/服务器通信的概念上,包含若干函数、类或资源的库文件,函数和数据被存储在一个DLL(服务器)上并由一个或多个客户导出而使用,这些客户可以是应用程序或者是其它的DLL。DLL库不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。
微软的Visual C++支持三种DLL,它们分别是Non-MFC Dll(非MFC动态库)、Regular Dll(常规DLL)、Extension Dll(扩展DLL)。Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被非MFC或MFC编写的应用程序所调用。Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有消息循环),被导出的函数是C函数、C++类或者C++成员函数(注意不要把术语C++类与MFC的微软基础C++类相混淆),调用常规DLL的应用程序不必是MFC应用程序,只要是能调用类C函数的应用程序就可以,它们可以是在Visual C++、Dephi、Visual Basic、Borland C等编译环境下利用DLL开发应用程序。
【编程基础】(以下多为引用)
由VS6或者VS2005时创建DLL工程只要按照提示一步步就行了。这里都是以VS2005,Non-MFC DLL。
Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库:
每一个DLL都必须有一个入口函数,默认是DllMain,这跟C里的main函数类似。用这个确省的入口函数就可以对DLL正确的初始化。典型的格式如下:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
对于进程线程的创建和释放时,需要进行的操作在case时调用就可以了。
导出DLL中的Symbols
DLL是包含若干个函数的库文件,应用程序使用DLL中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出这些函数有两种方法,一是在定义函数时使用导出关键字__declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。需要注意的是在使用第一种方法的时候,不能使用DEF文件。例子看附件。
- 使用__declspec(dllexport)来创建mydll.dll。该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码:
//MyDll.h extern "C" __declspec(dllexport) int Max(int a,int b); extern "C" __declspec(dllexport) int Min(int a,int b); //MyDll.cpp int Max(int a,int b){ return a>b?a:b; } int Min(int a,int b){ return a>b?b:a; }
该动态链接库编译成功后,打开MyDll工程中的debug目录,可以看到MyDll.dll、MyDll.lib两个文件。LIB文件中包含DLL文件名和DLL文件中的函数名等,该LIB文件只是对应该DLL文件的"映像文件",与DLL文件中,LIB文件的长度要小的多,在进行隐式链接DLL时要用到它。读者可能已经注意到在MyDll.h中有关键字"extern C",它可以使其他编程语言访问你编写的DLL中的函数。
- 用.def文件来创建DLL。
为了用.def文件创建DLL,请先删除上个例子创建的工程中的MyDll.h文件,保留MyDll.cpp并在该文件头删除#include MyDll.h语句,同时往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:
LIBRARY MyDll
EXPORTS
Max
Min
其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加 @n,如Max@1,Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到 MyDll.dll和MyDll.lib文件。
动态链接库DLL的链接
应用程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接。在使用DLL之前首先要知道DLL中函数的结构信息。Visual C++安装目录下提供了一个名为Dumpbin.exe的小程序,用它可以查看DLL文件中的函数结构。另外,Windows系统将遵循下面的搜索顺序来定位DLL: 1.包含EXE文件的目录,2.进程的当前工作目录, 3.Windows系统目录, 4.Windows目录,5.列在Path环境变量中的一系列目录。
1.隐式链接
隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字 __declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目为TestDll,在DllTest.h、DllTest.cpp文件中分别输入如下代码:
//DllTest.h //dll1.dll生成时用的def文件,不是用头文件生成的。 #pragma comment(lib,"dll1.lib") //如果生成DLL时使用了头文件,下面的声明和头文件的要相对应。 //比如在最前面加 extern "C" __declspec(dllimport) int Max(int a,int b); __declspec(dllimport) int Min(int a,int b); //DllTest.cpp #include <iostream> using namespace std; #include "testdll.h" int main(){ int a,b; while(cin>>a>>b){ cout< <"较大数:" << Max(a,b)<<endl; } return 0; }
在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的 System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,”dll1.lib”)语句,而直接在工程的Setting->Link页的Object/Moduls栏填入 MyDll.lib既可。
2.显式链接
显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。
void main(void) { typedef int(*pMax)(int a,int b); typedef int(*pMin)(int a,int b); HINSTANCE hDLL; PMax Max; HDLL=LoadLibrary("MyDll.dll");//加载动态链接库MyDll.dll文件; Max=(pMax)GetProcAddress(hDLL,"Max"); A=Max(5,8); Printf("比较的结果为%d",a); FreeLibrary(hDLL);//卸载MyDll.dll文件; }
在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用 FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。
使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用 MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为 GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。
4 Comments are ready?
你看看你转的文章,错误有多少……连代码都帖错了……
[回复]
汗…改了一些.有错再提下。
[回复]
受用了,API那块我还得多看《windows 核心编程》^_^
[回复]
boluor 回复 于 十二月 21st, 2009 at 11:41
API? 《windows核心编程》上第Ⅳ部分 动态链接库,好好看看,其中就有 DLL注入和API拦截 的例子。API你查手册就可以了,这本书上重点不是API。
[回复]
huangweiwei 回复 于 十二月 21st, 2009 at 16:04
恩呢,多谢指教。路漫漫其修远兮,吾将上下而求索 ^_^
[回复]
博主好好改改代码,
//DllTest.h
#pragma comment(lib,”dll1.lib”)
_declspec(dllimport) int Max(int a,int b);
_declspec(dllimport) int Min(int a,int b);
前面少了extern “C” .我调试了很长时间才看到。
还有,要注明基于VC6
[回复]
sandy 回复 于 五月 16th, 2010 at 22:00
其实你如果仔细看的话,作者是有说明这点的:“如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern “C”。”意思是如果不带extern “C”则使用的是def导出而不是dllimport关键字导出,只不过的确没有说的很明白,不仔细看的话看不到而已。
不过也不至于调试很长时间吧,缺少extern “C”导致的链接错误一眼就看出来了,马上就可以修正啊。
PS:我在GCC、VC6、VC2005上都测试通过了,只不过是把_declspec换成__declspec而已,为什么要注明“基于VC6”??
boluor你也稍作修改吧,免得新人继续挠头。。。
[回复]
boluor 回复 于 五月 16th, 2010 at 22:39
当时写的时候,用dll1.lib这名字而不是上面程序中出现的MyDll.lib,就是为了能够区别开。不过确实没有直接在代码中写注释更直观。我添加了注释。
[回复]