DLL入门
by windhawk
自己看DLL也有一个礼拜了,接下来准备开始学习如何写注入程序,在这之前,先来小结一下自己对DLL的理解。这篇文章只是一个DLL的入门文档,不会涉及太深的问题,给出了两个DLL小例子,希望帮助第一次接触DLL的同学们建立感性认识。
一、DLL
什么是DLL呢?DLL其实就是动态链接库的英文缩写(dynamic-link library, DLL),它被广泛地用于Windows操作系统。笼统地来说,DLL就是一段可执行共享代码,但是这段代码不能单独执行,必须由其他的进程或者线程来调用、启动。这样,同样一段代码可以同时被许多进程同时使用,当然,这里涉及全局变量和静态变量的部分在各个进程的地址空间中都有自己的一份备份,使得维护各个独立的进程。
如何理解DLL,我的理解是从两个角度来理解:
1.DLL作为链接库
DLL首先是个链接库。链接库用于为不同的程序提供共用的可执行代码。比如C中的printf(), scanf()等标准
I/O函数,各种程序都要用,为了方便就写成库的形式,用#include <stdio.h>的形式直接包含进来使用即可,大大提高了代码利用率的同时也降低了程序员的工作量。值得强调的是,这里的库文件已经链接到了.exe文件中,因此单独的.exe文件在任何PC上都可以运行。
2.DLL的动态性
进程好比一个程序运行所需要的所有资源的容器,进程执行的代码必须都在进程的私有地址空间中。所以库文件也必须加载到该进程的地址空间,普通的库文件加载是在程序加载时由系统的加载器负责的,先将程序的代码和数据加载进进程空间,再去检查导入段加载DLL,完成之后可能还会执行DllMain()函数进行一些初始化,完成之后才会将控制权交给主函数。如此内存中就会出现同个库文件的多个副本,比如printf(),在原本就稀缺珍贵的内存中这属于极度浪费的行为,而且也反过来限制了同时并发进程的数量。因此,人们将一部分库文件写成DLL,链接生成.exe文件时没有加入到其中,而是在程序运行需要时动态地加载进内存,而后映射到进程的虚拟地址空间,使用完毕后再予以释放。如此一来,库文件只在需要的时候占用内存,极大地提高了代码和内存的利用效率。这里值得强调的是,DLL文件只会在内存中加载一次,多个进程调用该DLL时只需要将内存地址(PA)映射到进程的虚拟地址空间即可,这其中,每个DLL的全局变量和静态变量在进程的地址空间中都有一份独立的拷贝,使得它们在多进程运行时不会相互影响。
二、简单的DLL程序——隐式加载DLL
在进一步叙述之前我们说明几个概念,DLL同正常的源文件编写一样,也包括函数和变量。其中只在DLL内部可以使用的函数或者变量我们统称为内部符号,而可以被外部的可执行文件调用的函数或者变量我们统称为导出符号。一个可以向外导出符号的模块我们称之为DLL 模块,调用外部导出符号的模块我们称之为可执行模块。模块是相对而言的,DLL模块也可以从另一个DLL模块中导入符号,从而变为可执行模块。
好了,现在考虑如果写一个DLL工程,首要的要解决DLL中导出符号和内部符号的区别,这就需要一个声明:system的头文件
_declspec(dllexports) 导出符号
_declspec(dllimports) 导入符号
现在,我们试着写我们的第一个DLL工程,这个工程中要包含DLL模块和可执行模块。我们在Visual Studio 2008下完成这个简单的项目。为了方便,我们统一放到一个解决方案下:
接下来,我们编写DLL模块:
首先是DLL头文件:
*******************************************************************************
/
/隐式加载DLL
//DLL头文件:
//头文件中需要声明DLL中的导出函数,
//为了简便,我们利用条件预编译头将程序文件中的导入函数声明也包含进来//然后在DLL文件中添加导出函数声明即可
#ifdef MYLIBAPI      //如果定义了符号,则什么也不做
#else//否则,定义导入符号
#define MYLIBAPI extern "C" _declspec(dllimport)
#endif
//用在可执行模块中的导入符号声明
MYLIBAPI int Add(int nLeft, int nRight);
PS:这里的extern “C”是告诉C++编译器不要对符号名进行改编,以防动态链接时不到目标符号,这是为了考虑对C的兼容性
*******************************************************************************
接下来是DLL源文件。DLL源文件并不神秘,除了没有main()函数,其他基本都与源文件一样。这里我们包含进刚才编写的DLL头文件,定义一个简单的求和函数。
******************************************************************************* #define MYLIBAPI extern"C"_declspec(dllexport)
#include"DLLHeader.h"
int Add(int nLeft, int nRight)
{
return nLeft + nRight;
}
*******************************************************************************
编译生成DLL文件后我们获得MyDLLTest.dll和MyDLLTest.lib两个文件。.DLL文件中存放着我们的执行代码,但是并不直接参与后续的.exe编译,而是由生成的库文件.lib代替同目标文件编译链接生成.exe。在.DLL文件中包含一个导出段,列出了该.DLL导出的符号和RVA(相对地址,便于后续寻址符号),而.lib文件则列出.DLL和导出符号的对应关系。我们可以用VS2008自带的dumpbin命令查看文件格式:
可以清晰看到其中导出的函数Add,左边第一个值为RVA-000110BE。
生成DLL后我们再来写可执行模块
*******************************************************************************
#include<stdio.h>
#include<stdlib.h>//system("pause")命令需要包含此头文件
#include"..\MyDLLTest\DLLHeader.h"//需要包含我们的DLL头文件
#pragma comment(lib, "..\\Debug\\MyDLLTest.lib")  //指明链接库文件所在地,起始目录从工程当前目录开始
int main(int argc, char *argv[])
{
int nLeft;
int nRight;
puts("请输入两个整数,用空格隔开:\n");
scanf("%d%d", &nLeft, &nRight);
printf("sum is %d\n", Add(nLeft, nRight));
system("pause");
return 0;
}
/*隐式加载DLL
因为需要在程序源文件中直接引用DLL中的导出符号,因而必须在文件头声明DLL 导入符号,并且指定
*.lib文件的位置,用于实际加载*.exe时查指定的DLL加载导入符号,如:导入符号--->*.lib--->.dll--->DLL的导出段--->导出符号的RVA
*/
*******************************************************************************查看下此时的文件

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。