通过钩子监听和获取windows登录密码(part 2)
2.DLL的编写
(注:这里介绍的DLL编写主要是非MFC的DLL编写。非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。有关DLL的编写,可以上网《VC++动态链接库(DLL)编程深入浅出》这一系列文章。)
要编写DLL,首先要知道什么是DLL,之后要知道DLL是由什么部分组成,最后要知道怎么去用DLL。
首先,先来说说什么是DLL吧。
DLL就是dynamic link library(动态链接库)的简称。DLL是一个包含函数和数据的模块,能被应用程序或者其他DLL所使用。它是代码重用和模块化的一种体现。以上是msdn上关于DLL的解释。当然,知道这个对本程序的编写并没有什么用处,只是例行说说而已。
然后,让我们来了解DLL的组成吧。
DLL由函数、类和数据组成(这是废话……哪个程序不都是这几个部分组成……)
DLL中能够定义两种函数:导出的(export)和内部的(internal)。导出的函数能被其他模块所调用,也能被该函数所在的DLL模块所调用。内部的函数只能被该函数所在的DLL所调用。
定义导出函数的语法:
A.extern "C" returnType __declspec(dllexport)functionName(arguments);(本文采取这个方法)
(注:extern "C"在这里是必要的。由于在其他模块调用DLL的导出函数的时候,我们是直接通过DLL编译后的函数名来调用的,如果用C++开发,则编译器会自动将函数名进行变化(没记错的话,这个变化是为了函数重载,尽管你在本程序中并不用到函数重载,但是编译器确实对函数名进行了变化,具体解释请查看extern "C"的作用)
B.采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。(有关.def的介绍,具体可以查查msdn)
下面的代码演示了怎样在.def文件将函数add声明为DLL导出函数(需在DLL所在目录下添加lib.def文件):
LIBRARY DLLNAME
EXPORTS
functionName @n
.def文件的规则为:
(1)LIBRARY语句说明.def文件相应的DLL;
(2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用:可以直接用序号调用函数);
(3).def文件中的注释由每个注释行开始处的分号(;)指定,且注释不能与语句共享一行。
说起DLL中的函数,有一个特殊函数:BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)有必要介绍一下:
(注:DllMain这个名字千万不能拼错,大小写也不能有错,拼错的话,系统就不会识别……那样会发生杯具滴……)
Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。和前面提及的入口函数不同的是,DLL的入口函数并不是必须的。这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。
根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接引用DllMain函数,DllMain是自动被调用的。
DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和 THREAD_DETACH。
APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;
进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。
最后,让我们来了解一下怎么用DLL吧。
//***************** DLL ****************************** /* 文件名:lib.h */ extern "C" int __declspec(dllexport)add(int x, int y); /* 文件名:lib.cpp */ #include "lib.h" int add(int x, int y) { return x + y; } //**************** End of DLL ************************ //***** Part of codes to use the DLL we defined ***** #include <stdio.h> #include <windows.h> typedef int(*lpAddFun)(int, int); //宏定义函数指针类型 int main(int argc, char *argv[]) { HINSTANCE hDll; //DLL句柄 lpAddFun addFun; //函数指针 hDll = LoadLibrary("path_to_dll");//加载DLL if (hDll != NULL) { addFun = (lpAddFun)GetProcAddress(hDll, "add"); if (addFun != NULL) { int result = addFun(2, 3); printf("%d", result); } FreeLibrary(hDll);//当不再用该DLL时,应该把该DLL从内存中卸载掉。 } return 0; }
说明:
首先,语句typedef int (* lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun;
其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;
再次,在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用;
最后,应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。
通过这个简单的例子,我们获知DLL定义和调用的一般概念:
(1)DLL中需以某种特定的方式声明导出函数(或变量、类);
(2)以某种特定的方式调用DLL的导出函数(或变量、类)。
(注:上面的方法并不是唯一调用DLL的方法。下面这个方法也可以调用DLL,而且也是更安全,不容易出错的方法。)
#include <stdio.h> #pragma comment(lib,"dllName.lib") extern int functionName(arguments); int main(int argc, char *argv[]) { functionName(arguments) //这里就可以直接使用, 无须进行强制指针转换 return 0; }
通过#pragma comment(lib,"dllName.lib")方式导入的DLL是静态导入,这意味着该DLL在链接的时候就已经链接上了,而不是运行时动态地链接。所以严格来说,这个方法是导入静态链接库,而不是DLL。但是这个方法的好处在于,不用手动管理DLL。