DLL学习摘记

来源:互联网 发布:js 对象数组 remove 编辑:程序博客网 时间:2024/06/07 20:05


DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。而对于DLL的学习,主要知识点摘记如下.


1.kernel32.dll是Windows 9x/Me中非常重要的32位动态链接库文件,属于内核级文件。它控制着系统的内存管理、数据的输入输出操作和中断处理,当Windows启动时,kernel32.dll就驻留在内存中特定的写保护区域,使别的程序无法占用这个内存区域。



2.user32.dll是Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息。



3.系统文件gdi32.dll是存放在Windows系统文件夹中的重要文件,通常情况下是在安装操作系统过程中自动创建的,对于系统正常运行来说至关重要. 包含的函数用来绘制图像和显示文字。



4.vcvarsall.bat是vs里面自带的文件,作用整体上来说就是配置环境变量、工作目录。可以简单看看里面的批处理命令。就是根据本地电脑的配置再次调用另外一个批处理文件。

然后指定调用相应的工具如:cl.exe  、 link.exe 、lib.exe等,路径都在vs安装目录下面,vc\bin  目录,一看便知道。

在命令行界面执行该文件后,该文件所设置的环境信息只是在当前命令行窗口生效,关闭该窗口并重新打开后,需要重新运行该文件进行环境信息配置.



5.应用程序如果想要访问某个DLL中的函数,那么该函数必须是已经被导出的函数(即加了标识符_declspec(dllexport)).为了查看一个DLL中有哪些导出的函数,可以利用VS的命令行工具Dumpbin来实现.如查看DLL1.DLL文件中的导出函数,可以在命令行界面DLL1.DLL文件目录下运行dumpbin -exports DLL1.dll.


6.Depends.exe 是用来反编译VC程序的工具,可以查看PE模块的导入模块以及导入和导出的函数,以及动态剖析PE模块的依赖性和解析C++的函数名称, 可分析dll和exe所依赖的dll。可以看到dll以及dll的函数,可以查看导入导出函数,挺好用的.可以通过 开始/程序/VS/VSTOOLS/打开DEPENDS,然后通过打开文件查看。
主要功能如下:
该工具得到的是你软件中隐式链接的Dll库,也就是用lib关联的Dll模块,无法显示显式链接的Dll模块,也就是用LoadLibrary函数导入的Dll函数

查看 PE 模块的导入模块
查看 PE 模块的导入和导出函数
动态剖析 PE 模块的模块依赖性
解析 C++ 函数名称



7.__declspec(dllimport),变量的导入。

特别要注意的是用extern 声明所导入的并不是DLL中全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。

通过__declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式.






8.导出成员用_declspec(dllexport),导入用_declspec(dllimport),可以在头文件的声明部分使用这两个修饰符对函数,类,变量,或类成员函数进行修饰,来表示该成员是一个导入成员或导出成员。

有一个小技巧,就是使用宏定义作为开关控制,让dll项目和调用dll的项目(函数,C++类等)公用一个头文件:
1>. dll的代码源文件(cpp文件)中定义一个宏 然后包含头文件

#define DLLAPI _declspec(dllexport)
#include 
"header.h"

2>. 头文件header.h中这样写

#ifdef DLLAPI
#else
#define DLLAPI _declspec(dllimport)
#endif

3>. 然后就可以在header.h的需要导出或导入的成员用DLLAPI来修饰

void DLLAPI func(void);



这样,在执行宏替换的时候,在dll的代码源文件编译的时候DLLAPI会被替换位_declspec(dllexport),而在调用方,因为没有定义过DLLAPI,所以会被替换成_declspec(dllimport)。



9..extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++和C对产生的函数名字的处理是不一样的.

在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。

但是这种方法只能导出全局函数,不能导出一个类的成员函数。


10.__stdcall是函数调用约定的一种,函数调用约定主要约束了两件事:

1>.参数传递顺序
2>.调用堆栈由谁(调用函数或被调用函数)清理
常见的函数调用约定:stdcall cdecl fastcall thiscall naked call
__stdcall表示
1>.参数从右向左压入堆栈
2>.函数被调用者修改堆栈
3>.函数名(在编译器这个层次)自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见。

MFC缺省调用约定编辑
1>、_stdcall是Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式,
自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。 int f(void *p) -->> _f@4(在外部汇编语言里可以用这个名字引用这个函数)
2>、C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。
3>、__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。
4>、thiscall仅仅应用于“C++”成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
5>、naked call。 当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。
(这些代码称作 prolog and epilog code,一般,ebp,esp的保存是必须的).
但是naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。
关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。







11.模块定义 (.def) 文件为链接器提供有关被链接程序的导出、属性及其他方面的信息。生成 DLL 时,.def 文件最有用。由于存在可代替模块定义语句使用的链接器选项,通常不需要 .def 文件。也可以将 __declspec(dllexport) 用作指定导出函数的手段。在链接器阶段可以使用/DEF(指定模块定义文件)链接器选项调用 .def 文件。
如果生成的 .exe 文件没有导出,使用 .def 文件将使输出文件较大并降低加载速度。

.def 文件中的第一条 LIBRARY 语句不是必须的,但LIBRARY 语句后面的 DLL 的名称必须正确,即与生成的动态链接库的名称必须匹配。此语句将 .def 文件标识为属于 DLL。链接器将此名称放到 DLL 的导入库中。
EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。
LIBRARY BTREE
EXPORTS
Insert @1
Delete @2
Member @3
Min @4
如果使用 MFC DLL 向导创建 MFC DLL,则向导将为您创建主干 .def 文件并将其自动添加到项目中。添加要导出到此文件的函数名。对于非 MFC DLL,必须亲自创建 .def 文件并将其添加到项目中。
如果导出 C++ 文件中的函数,必须将修饰名放到 .def 文件中,或者通过使用外部“C”定义具有标准 C 链接的导出函数。如果需要将修饰名放到 .def 文件中,则可以通过使用 DUMPBIN 工具或 /MAP链接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。如果将 Visual C++ 编译器产生的修饰名放到 .def 文件中,则链接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .def 文件中的导出名相匹配。


12.DLL两种加载方式:所谓的静态调用DLL是指程序加载的时候直接就把需要的DLL全部加载了,一直到程序运行结束才释放这些加载的DLL,这个就是所谓的静态加载,动态加载就是需要一个DLL中某个函数的时候加载这个DLL运行完成了这个函数就释放DLL,这个就是动态加载!



13.LoadLibrary()载入指定的动态链接库,并将它映射到当前进程使用的地址空间。一旦载入,即可访问库内保存的资源.

HMODULE  LoadLibrary( LPCTSTR lpFileName);

lpLibFileName String,指定要载入的动态链接库的名称。采用与CreateProcess函数的lpCommandLine参数指定的同样的搜索顺序

返回值 Long,成功则返回库模块的句柄,零表示失败。会设置GetLastError 

一旦不需要,用FreeLibrary函数释放DLL.



14.GetProcAddress函数检索指定的动态链接库(DLL)中的输出库函数地址。
函数原型:
FARPROC GetProcAddress(
HMODULE hModule, // DLL模块句柄
LPCSTR lpProcName // 函数名
);
返回值:
如果函数调用成功,返回值是DLL中的输出函数地址。
如果函数调用失败,返回值是NULL。得到进一步的错误信息,调用函数GetLastError。



15.MAKEINTRESOURCE是一个资源名转换的宏,这个宏是把一个数字类型转换成指针类型的宏,它不存在释放的问题·
用这个宏的主要原因是有的资源是用序号定义的,而不是字符串.所以要把数字转换成字符串指针,然后再传递给LoadResource之类的函数,这样才加载了资源.
要释放资源(用LoadResource加载的)可以调用FreeResource函数把LoadResource返回的指针传递给FreeResource.



16.每个DLL都可以有一个入口点函数DllMain,系统会在不同的时刻调用此函数。以下是DllMain的一般形式:

BOOL WINAPI DllMain( HINSTANCE hinstDLL, // handle to DLL module

    DWORD fdwReason, // reason for calling function

    LPVOID lpReserved ) // reserved





0 0
原创粉丝点击