Windows编程-动态链接库
动态链接库的加载 :
隐式加载:
隐式加载又叫载入时加载,指在主程序载入内存时搜索DLL,并将DLL载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受。
显式加载:
显式加载又叫运行时加载,指主程序在运行过程中需要DLL中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好。
显式加载动态链接库时,需要用到 LoadLibrary() 函数,该函数的作用是将指定的可执行模块映射到调用进程的地址空间。LoadLibrary() 函数的原型声明如下所示:
HMODULE LoadLibrary(LPCTSTR 1pFileName);
显式调用动态库步骤:
1、创建一个函数指针,其指针数据类型要与调用的 DLL 引出函数相吻合。
2、通过 Win32 API 函数LoadLibrary()显式的调用DLL,此函数返回DLL 的实例句柄。
3、通过 Win32 API 函数GetProcAddress()获取要调用的DLL 的函数地址,把结果赋给自定义函数的指针类型。
4、使用函数指针来调用 DLL 函数。
5、最后调用完成后,通过 Win32 API 函数FreeLibrary()释放DLL 函数。
LoadLibrary() 函数不仅能够加载DLL(.dll),还可以加载可执行模块(.exe)。一般来说,当加载可执行模块时,主要是为了访问该模块内的一些资源,例如位图资源或图标资源等。
LoadLibrary() 函数有一个字符串类型(LPCTSTR)的参数,该参数指定了可执行模块的名称,既可以是一个.dll文件,也可以是一个.exe文件。
如果调用成功, LoadLibrary() 函数将返回所加载的那个模块的句柄。该函数的返回类型是HMODULE。 HMODULE类型和HINSTANCE类型可以通用。
当获取到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,这可以通过调用 GetProcAddress() 函数来实现。
该函数用来获取DLL导出函数的 地址,其原型声明如下所示: FARPROC GetProcAddress(HMODULE hModule, LPCSTR 1pProcName);
可以看到,GetProcAddress函数有两个参数,其含义分别如下所述:
- hModule:指定动态链接库模块的句柄,即 LoadLibrary() 函数的返回值。
- 1pProcName:字符串指针,表示DLL中函数的名字。
DLL的进入点函数:
- BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReasin,PVOID fimpload)
- 参数hinstDll包含了DLL的实例句柄。与(w)WinMain函数的hinstExe参数一样,这个值用于标识DLL的文件映像被映射到进程的地址空间中的虚拟内存地址。通常应将这个参数保存在一个全局变中,这样就可以在调用加载资源的函数(如DialogBox和LoadString)时使用它。
- 参数是 fImpLoad,如果DLL是隐含加载的,那么该参数将是个非 0值,如果DLL是显式加载的,那么它的值是0。
- 参数fdwReason用于指明系统为什么调用该函数。
- 当DLL被初次映射到进程的地址空间中时,系统将调用该DLL的DllMain函数,给它传递参数fdwReason的值DLL_PROCESS_ATTACH。只有当DLL的文件映像初次被映射时,才会出现这种情况。如果线程在后来为已经映射到进程的地址空间中的DLL调用LoadLibrary(Ex)函数,那么操作系统只是递增DLL的使用计数,它并不再次用DLL_PROCESS_ATTACH的值来调用DLL的DllMain函数。
- DLL从进程的地址空间中被卸载时,系统将调用DLL的DllMain函数,给它传递fdwReason的值DLL_PROCESS_DETACH。当DLL处理这个值时,它应该执行任何与进程相关的清除操作。
- 当在一个进程中创建线程时,系统要查看当前映射到该进程的地址空间中的所有 DLL文件映像,并调用每个文件映像的带有DLL_THREAD_ATTACH值的DllMain函数。这可以告诉所有的DLL执行每个线程的初始化操作。新创建的线程负责执行DLL的所有DllMain函数中的代码。只有当所有的DLL都有机会处理该通知时,系统才允许新线程开始执行它的线程函数。
注意:
- DLL使用DllMain函数来对它们进行初始化。 当你的DllMain函数执行时,同一个地址空间中的其他DLL可能尚未执行它们的DllMain函数。这意味着它们尚未初始化,因此你应该避免调用从其他DLL中输入的函数。此外,你应该避免从DllMain内部调用LoadLibrary(Ex)和FreeLibrary函数,因为这些函数会形式一个依赖性循环。 你的DllMain函数只应该进行一些简单的初始化,比如设置本地存储器,创建内核对象和打开文件等。你还必须避免调用User、 hell、ODBC、COM、RPC和套接字函数(即调用这些函数的函数),因为它们的DLL也许尚未初始化 ,或者这些函数可能在内部调用LoadLibrary(Ex)函数,这同样会形成一个依赖性循环。另外,如果创建全局性的或静态的C + +对象,那么应该注意可能存在同样的问题,因为在你调用DllMain函数的同时,这些对象的构造函数和析构函数也会被调用。
- 如果因为系统中的某个线程调用了TerminateProcess而使进程终止运行,那么系统将不调用带有DLL_PROCESS_DETACH值的DLL的DllMain函数。这意味着映射到进程的地址空间中的任何DLL都没有机会在进程终止运行之前执行任何清除操作。这可能导致数据的丢失。只有在迫不得已的情况下,才能使用TerminateProcess函数。当一个新D L L被映射到进程的地址空间中时,如果该进程内已经有若干个线程正在运行,那么系统将不为现有的线程调用带有DLL_THREAD_ ATTACH值的DDL的DllMain函数。只有当新线程创建时D L L被映射到进程的地址空间中,它才调用带有DLL_THREAD_ATTACH值的DLL的DllMain函数。