DLL是一个包含函数和数据的模块, 它们可以被其他模块(应用程序或DLL)使用。
DLL可以定义两种函数: <1>导出函数 <2>内部函数 .
导出函数可以被内部或其他模块调用。
内部函数只能在DLL内部被调用。
- About Dynamic-Link Libraries
动态连接 允许一个模块在加载或运行时,仅仅只需包含定位一个动态库导出函数的信息,而无需将这个动态库整个编译进模块中。
调用一个DLL中的函数,有两种方法:
<1> load-time dynamic linking.(加载时动态链接), 是指显示地调用动态库中导出函数,就像这些函数在本地一样。 这需要你将模块与DLL对应的导入库文件进行链接。
<2> run-time dynamic linking(运行时动态链接), 在运行时使用LoadLibrary或者LoadLibraryEx函数来加载动态库。 当模块被加载后,使用GetProcAddress函数来获取动态库中导出的函数的地址。
DLL和内存管理:
每个进程将DLL加载到它的虚拟地址空间, 当DLL被加载到进程的虚拟地址空间后,他就可以调用DLL的导出函数了。
系统为每个引用的DLL维护一个引用计数,当一个线程加载DLL,引用计数会加1,当进程结束,引用计数减1,, 当引用计数为0时,DLL会被从虚拟地址空间中卸载。
与其他函数一样,一个导出的DLL函数运行在调用它的线程的上下文中,所以:
<1>进程中调用DLL的线程可以使用 被DLL函数打开的句柄,相似地,由线程打开的句柄也可以被DLL函数使用。
<2>DLL使用调用它的线程的栈, 使用调用它的进程的虚拟地址空间。
<3>DLL从调用它的进程的虚拟地址空间中分配内存。
动态链接库的优点:(相对于静态链接库)
<1>节省内存。 当多个进程加载相同的DLL时,DLL只会在物理内存中加载一份DLL, 每个进程加载DLL在物理内存中同一个基地址。
<2>当DLL的函数的实现改变,而函数声明没变的情况下,从新生成DLL。应用程序不需要重新编译和链接。
<3>不同编程语言写的程序可以调用相同的DLL函数,只要遵循调用约定。
调用约定(如C , Pascal , 或者标准调用) 控制调用函数参数的压栈顺序,是否调用函数,还是被调用函数负责清理栈,是否任何参数被传递到寄存器中等。
使用动态库的潜在的缺点是: 引用程序不是自包含的: DLL依赖于DLL的存在。
当使用load-time dynamic linking, 如果应用程序需要的DLL不存在,那么系统将结束这个进程。
当使用run-time dynamic linking, 如果需要的DLL不存在,系统不会结束该进程,但是DLL导出还是不可用。
创建动态链接库
线程安全: 如果你的DLL被多线程应用程序使用,那必须对所有DLL数据进行同步,以避免数据污染。
如果要使用load-time dynamic linking, 必须创建一个导入库(.lib文件).
导出函数的两个方法:
<1>使用 __declspec 修饰符
<2>使用.def文件
创建导入库: .lib文件。 如何创建导入库?
动态链接库的入口点函数
动态库可以可选地指定一个入口点函数。 如果指定了,当一个进程或线程加载或卸载动态库时,系统都会执行入口点函数。 它可以被用来执行一些初始化和清理任务。
DllMain是一个占位符,当创建自己的DLL时,必须指定实际的入口点函数名字。
入口函数定义:DLL入口点函数必须以标准调用约定(standard-call calling convention)定义
DLL的导入库(即与dll文件相对应的.lib文件),提供系统加载动态库所需要的信息;并且提供定位动态库中的导出函数信息。
01 动态链接库基础