//1. (A):为了让线程能够调用DLL模块中的函数,我们必须将DLL的文件映像映射到调用线程所在的进程的地址空间,有两种方式可以达到此目的: (1):直接让应用程序的源代码引用DLL中所包含的符号,这使得加载程序会在应用程序运行的时候隐式地载入并链接所需DLL (2):在应用程序运行过程中,显示的载入所需的DLL并显示地与想要的输出符号进行链接(此方法不需要用到lib文件) (B): HMODULE WINAPI LoadLibrary ( _In_ LPCTSTR lpFileName //The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file) ); Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded. If the function succeeds, the return value is a handle to the module. If the function fails, the return value is NULL (C): HMODULE WINAPI LoadLibraryEx ( _In_ LPCTSTR lpFileName, _Reserved_ HANDLE hFile, //This parameter is reserved for future use. It must be NULL. _In_ DWORD dwFlags ); Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded. dwFlags 常用属性: (1)DONT_RESOLVE_DLL_REFERENCES: If this value is used, and the executable module is a DLL, the system does not call DllMain for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module. Note Do not use this value; it is provided only for backward compatibility. If you are planning to access only data or resources in the DLL, use LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE or both. Otherwise, load the library as a DLL or executable module using the LoadLibrary function. (2)LOAD_LIBRARY_AS_DATAFILE: If this value is used, the system maps the file into the calling process's virtual address space as if it were a data file. Nothing is done to execute or prepare to execute the mapped file. Therefore, you cannot call functions like GetModuleFileName, GetModuleHandle or GetProcAddress with this DLL. Using this value causes writes to read-only memory to raise an access violation. Use this flag when you want to load a DLL only to extract messages or resources from it. This value can be used with LOAD_LIBRARY_AS_IMAGE_RESOURCE. (3):LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE Similar to LOAD_LIBRARY_AS_DATAFILE, except that the DLL file is opened with exclusive write access for the calling process. Other processes cannot open the DLL file for write access while it is in use. However, the DLL can still be opened by other processes. This value can be used with LOAD_LIBRARY_AS_IMAGE_RESOURCE (4):LOAD_LIBRARY_AS_IMAGE_RESOURCE: If this value is used, the system maps the file into the process's virtual address space as an image file. However, the loader does not load the static imports or perform the other usual initialization steps. Use this flag when you want to load a DLL only to extract messages or resources from it. Unless the application depends on the file having the in-memory layout of an image, t his value should be used with either LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_DATAFILE. //详情或其他属性见 https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx 即便 LoadLibrary 和 LoadLibraryEx 载入的DLL是磁盘上的同一文件,我们也不应该将其返回的映射地址互换使用 (D): BOOL WINAPI FreeLibrary(_In_ HMODULE hModule); Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer valid. The system maintains a per-process reference count for each loaded module. A module that was loaded at process initialization due to load-time dynamic linking has a reference count of one. The reference count for a module is incremented each time the module is loaded by a call to LoadLibrary. The reference count is also incremented by a call to LoadLibraryEx unless the module is being loaded for the first time and is being loaded as a data or image file. The reference count is decremented each time the FreeLibrary or FreeLibraryAndExitThread function is called for the module. When a module's reference count reaches zero or the process terminates, the system unloads the module from the address space of the process. Before unloading a library module, the system enables the module to detach from the process by calling the module's DllMain function, if it has one, with the DLL_PROCESS_DETACH value. Doing so gives the library module an opportunity to clean up resources allocated on behalf of the current process. After the entry-point function returns, the library module is removed from the address space of the current process. It is not safe to call FreeLibrary from DllMain. Calling FreeLibrary does not affect other processes that are using the same module. Use caution when calling FreeLibrary with a handle returned by GetModuleHandle. The GetModuleHandle function does not increment a module's reference count, so passing this handle to FreeLibrary can cause a module to be unloaded prematurely. A thread that must unload the DLL in which it is executing and then terminate itself should call FreeLibraryAndExitThread instead of calling FreeLibrary and ExitThread separately. Otherwise, a race condition can occur. (E): HMODULE WINAPI GetModuleHandle(_In_opt_ LPCTSTR lpModuleName); Retrieves a module handle for the specified module. The module must have been loaded by the calling process. lpModuleName: If this parameter is NULL, GetModuleHandle returns a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag If the function succeeds, the return value is a handle to the specified module. If the function fails, the return value is NULL (F): FARPROC WINAPI GetProcAddress ( _In_ HMODULE hModule, _In_ LPCSTR lpProcName //此参数最好别传递序号,最好直接使用名称 ); Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). (G): BOOL WINAPI DllMain //此函数只应该执行简单的初始化,不应该执行复杂的初始化,因为包含了复杂调用的函数的DLL可能尚未初始化完毕 ( _In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved ); An optional entry point into a dynamic-link library (DLL). When the system starts or terminates a process or thread, it calls the entry-point function for each loaded DLL using the first thread of the process. The system also calls the entry-point function for a DLL when it is loaded or unloaded using the LoadLibrary and FreeLibrary functions. fdwReason [in] The reason code that indicates why the DLL entry-point function is being called. This parameter can be one of the following values. (1):DLL_PROCESS_ATTACH: The DLL is being loaded into the virtual address space of the current process as a result of the process starting up or as a result of a call to LoadLibrary. DLLs can use this opportunity to initialize any instance data or to use the TlsAlloc function to allocate a thread local storage (TLS) index. The lpReserved parameter indicates whether the DLL is being loaded statically or dynamically. (2):DLL_PROCESS_DETACH: The DLL is being unloaded from the virtual address space of the calling process because it was loaded unsuccessfully or the reference count has reached zero (the processes has either terminated or called FreeLibrary one time for each time it called LoadLibrary). The lpReserved parameter indicates whether the DLL is being unloaded as a result of a FreeLibrary call, a failure to load, or process termination. The DLL can use this opportunity to call the TlsFree function to free any TLS indices allocated by using TlsAlloc and to free any thread local data. Note that the thread that receives the DLL_PROCESS_DETACH notification is not necessarily the same thread that received the DLL_PROCESS_ATTACH notification. (3):DLL_THREAD_ATTACH: The current process is creating a new thread. When this occurs, the system calls the entry-point function of all DLLs currently attached to the process. The call is made in the context of the new thread. DLLs can use this opportunity to initialize a TLS slot for the thread. A thread calling the DLL entry-point function with DLL_PROCESS_ATTACH does not call the DLL entry-point function with DLL_THREAD_ATTACH. Note that a DLL's entry-point function is called with this value only by threads created after the DLL is loaded by the process. When a DLL is loaded using LoadLibrary, existing threads do not call the entry-point function of the newly loaded DLL. (4):DLL_THREAD_DETACH: A thread is exiting cleanly. If the DLL has stored a pointer to allocated memory in a TLS slot, it should use this opportunity to free the memory. The system calls the entry-point function of all currently loaded DLLs with this value. The call is made in the context of the exiting thread. When the system calls the DllMain function with the DLL_PROCESS_ATTACH value, the function returns TRUE if it succeeds or FALSE if initialization fails. If the return value is FALSE when DllMain is called because the process uses the LoadLibrary function, LoadLibrary returns NULL. (The system immediately calls your entry-point function with DLL_PROCESS_DETACH and unloads the DLL.) If the return value is FALSE when DllMain is called during process initialization, the process terminates with an error. When the system calls the DllMain function with any value other than DLL_PROCESS_ATTACH, the return value is ignored. (H):DllMain 的序列化调用:多个线程响应DLL的 DLL_THREAD_ATTACH 是依次进行的,若其中一个被 DLL_THREAD_ATTACH 中的代码所阻塞,那么新建的线程也将被阻塞 (I):如果没有定义自己的 DllMain 那将调用C/C++运行库提供的 DllMain 函数 (J): 动态导入DLL例子: DLL构建 Tool.h: #pragma once #ifndef SznDllTest #define SznDllTest __declspec(dllimport) #endif extern "C" SznDllTest void FunTest(); extern "C" SznDllTest int nValue; class SznDllTest CTest { public: CTest(); }; DLL构建 Tool.cpp #define SznDllTest __declspec(dllexport) #include <cstdio> #include <assert.h> #include <windows.h> #include "Tool.h" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: { printf("Process Attach "); break; } case DLL_PROCESS_DETACH: { printf("Process Detach "); break; } case DLL_THREAD_ATTACH: { printf("Thread Attach "); break; } case DLL_THREAD_DETACH: { printf("Thread Detach "); break; } default: { assert(false); } } return TRUE; } void FunTest() { printf("FunTest "); } int nValue = 1024; CTest::CTest() { printf("CTest "); } 可执行文件cpp: #include <windows.h> #include <assert.h> #include <process.h> unsigned int __stdcall FunThread(void* pVoid) { Sleep(100); return 0; } int main() { HINSTANCE hTestDll = NULL; if (!GetModuleHandle(TEXT("Test.dll"))) { hTestDll = LoadLibrary(TEXT("Test.dll")); //输出 Process Attach } auto it = GetModuleHandle(TEXT("Test.dll")); //it = 0x0f9a0000 hTestDll = 0x0f9a0000 HANDLE hThread = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 0, FunThread, nullptr, 0, nullptr)); Sleep(10); //输出 Thread Attach WaitForSingleObject(hThread, INFINITE); //输出 Thread Detach CloseHandle(hThread); reinterpret_cast<void (*)()>(GetProcAddress(hTestDll, "FunTest"))(); //输出 FunTest int nTem = *reinterpret_cast<int*>(GetProcAddress(hTestDll, "nValue")); //nTem = 1024 auto it1 = GetProcAddress(hTestDll, "CTest"); //it1 = 0x00000000 if (!FreeLibrary(hTestDll)) //输出 Process Detach { assert(false); } return 0; } //2.(备注:关于延迟导入还有很多细节,详情见书指定章节,下面仅介绍如何使用) (A):延迟载入DLL优点: (1):提高程序初始化速度,让DLL载入过程延伸到进程的执行过程中 (2):如果我们代码中调用一个较新的函数,然后又可能在一个不提供该函数的老版本的操作系统下运行该应用程序,则可根据系统版本来决定是否使用新函数 (B):延迟载入的局限性: (1):一个导出全局变量的DLL是无法延迟载入的 (2):Kernel32.dll无法延时载入 (3):不应该在 DllMain 的入口点函数中调用一个延时载入的函数,否则可能导致崩溃 (C):延迟载入的DLL是隐式链接的,系统一开始不会将该DLL载入,只有当我们的代码试图去引用DLL中包含的一个符号时,系统才会实际载入该DLL (D):指定延迟加载的DLL: (1):项目->属性->配置属性->链接器->输入->延迟加载的DLL 设置希望进行延迟加载的DLL (2):#include "XXX.h" 添加DLL对应的头文件 #pragma comment(lib, "XXX.lib") 设置lib (3):#include <DelayImp.h> 提供关于延迟加载DLL的函数支持 #pragma comment(lib, "Delayimp.lib") 提供关于延迟加载DLL的lib支持 (E):延迟载入DLL的注意点: (1):通常当操作系统加载程序载入我们的可执行文件时,会试图载入必须的DLL,如果无法载入一个DLL,加载程序会显示一条错误消息。 但对延迟载入的DLL来说,在初始化的时候加载程序不会检查该DLL是否存在,如果当一个延迟载入函数被调用的时候加载程序无法找到该DLL,那么程序会抛出一个异常 (2):如果延迟载入函数对应DLL存在,但在DLL中找不到试图调用的函数时,也会抛出一个软件异常 (F):卸载延迟导入的DLL: (1):项目->属性->配置属性->链接器->高级->卸载延迟加载的DLL 选择是 (2):调用 __FUnloadDelayLoadedDLL2 来卸载指定的DLL(此函数参数指定DLL名称,不应该含有路径) (3):确保不对延迟载入的 DLL 调用 FreeLibrary ,否则函数地址不会被重置,则下次访问对应函数时候会出错 (G):延迟载入时的挂钩函数 函数转发器 暂不研究 (H):延迟载入DLL的例子: Tool.h(用于构建DLL) #pragma once #ifndef SznDllTest #define SznDllTest __declspec(dllimport) #endif extern "C" SznDllTest void FunTest(); extern "C" SznDllTest int nValue; class SznDllTest CTest { public: CTest(); }; Tool.cpp(用于构建DLL) #define SznDllTest __declspec(dllexport) #include "Tool.h" #include <cstdio> #include <windows.h> int __stdcall DllMain(HINSTANCE hIns, DWORD nReason, void* pTem) { switch(nReason) { case DLL_PROCESS_ATTACH: { printf("Dll Process Attach "); break; } case DLL_PROCESS_DETACH: { printf("Dll Process Detach "); break; } default: { break; } } return true; } void FunTest() { printf("FunTest "); } int nValue = 1024; CTest::CTest() { printf("CTest "); } Del.cpp(用于构建exe) #include <windows.h> #include <DelayImp.h> #include "Tool.h" #pragma comment(lib, "Test.lib") #pragma comment(lib, "Delayimp.lib") int main() { HANDLE hDll0 = GetModuleHandle(TEXT("Test.dll")); //hDll0 = 0x00000000 FunTest(); //输出两行: Dll Process Attach FunTest //int nValue1 = nValue; fatal error LNK1194: 无法延迟加载“Test.dll”,原因在于数据符号“__imp__nValue”的导入;链接时不使用 /DELAYLOAD:Test.dll HANDLE hDll1 = GetModuleHandle(TEXT("C:\Users\szn\Desktop\Del\Del\Del\Test.dll")); //hDll1 = 0x5c020000 BOOL nRe = __FUnloadDelayLoadedDLL2("Test.dll"); //nRe = 1 输出 Dll Process Detach HANDLE hDll2 = GetModuleHandle(TEXT("C:\Users\szn\Desktop\Del\Del\Del\Test.dll")); //hDll2 = 0x00000000 CTest Test; //输出两行: Dll Process Attach CTest BOOL nRe1 = __FUnloadDelayLoadedDLL2("C:\Users\szn\Desktop\Del\Del\Del\Test.dll"); //nRe1 = 0 return 0; } //3. 模块地址的重定位: (A):每个可执行文件和DLL模块都有一个首选基地址,他表示在将模块映射到进程的地址空间时候的最佳内存位置 (B):DLL中的变量的虚拟地址会被固定并保存在磁盘上的文件映像中,只有当DLL被载入到他的首选基地址时,这些内存地址才是绝对正确的 如果加载程序无法将模块载入他的首选基地址,那么系统会打开模块的重定位段,并遍历其中的所有条目来更正模块首选地址与实际基地址之间的差异 这一过程会触发写时复制功能,并将占用页交换文件 (C):项目->属性->配置属性->链接器->高级->基址 可以指定基地址 //4. 此章更多细节,还是得参考原书章节