HMODULE LoadLibrary(PCTSTR pszDLLPathName);
函数返回的HMODULE值表示文件映像被映射到的虚拟内在地址.
BOOL FreeLibrary(HMODULE hInstDll);
VOID FreeLibraryAndExitThread(HMODULEhInstDll, DWORD dwExitCode);
系统会在每个进程中为每个DLL维护一个使用计数.
HMODULE GetModuleHandle(PCTSTRpszModuleName);
如果传NULL给GetModuleHandle,那么函数会返回应用程序的可执行文件的句柄.
如果只有一个DLL或(.exe)的HINSTANCE/HMODULE,那么我们同样可以通过GetModuleFileName函数来得到该DLL的全路径.
DWORD GetModuleFileName(HMODULEhInstModule, PTSTR pszPathName, DWORD cchPath);
如果传NULL给hInstModule参数,那么GetModuleFileName会在pszPathName参数中返回当前正在运行的应用程序的可执行文件的文件名.
FARPROC GetProcAddress(HMODULE hInstDll, PCSTR, pszSymblName);
FARPROC fn = GetProcAddress(hInstDll,“SomeFuncInDll”);
FARPROC fn = GetProcAddress(hInstDll,MAKEINTRESOURCE(2));
不建议使用第2种方式.
一个DLL可以有一个入口点函数.系统会在不同的时候调用这个入口点函数.DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH,DLL_THREAD_DETACH,DLL_PROCESS_DETACH.
当系统第一次将一个DLL映射到进程的地址空间时,会调用DllMain函数,并在fdwReason参数中传入DLL_PROCESS_ATTACH.只有当DLL的文件映像第一次被映射的时候,才会这样.
当DllMain处理DLL_PROCESS_ATTACH通知的时候,DllMain的返回值用来表示该DLL的初始化是否成功.如果fdwReason是任何其他的值,那么系统将忽略DllMain的返回值.
创建新的进程时,系统会分配进程地址空间并将.exe的文件映像以及所需DLL的文件映像映射到进程的地址空间中.然后,系统将创建进程的主线程并用这个线程来调用每个DLL的DllMain函数,同时传入DLL_PROCESS_ATTACH.
当系统将一个DLL从进程的地址空间中撤销映射时,会调用DLL的DllMain函数,并在fdwReason参数中传入DLL_RPOCESS_DETACH.
如果进程终止是因为系统中的某个线程调用了TerminateProcess,系统便不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数.
当进程创建一个线程的时候,系统会检查当前映射到进程的地址空间中的所有DLL文件映射,并用DLL_THREAD_ATTACH来调用每个DLL的DllMain函数.
线程return或ExitThread告诉系统该线程想要终止,但系统不会立即终止该线程,而会让这个即将终止的线程用DLL_THREAD_DETACH来调用所有已映射DLL的DllMain函数.
如果线程终止是因为系统中的某个线程调用了TerminateThread,那么系统不会用DLL_THREAD_DETACH来调用所有DLL的DllMain函数.
系统会将对DLL的DllMain函数的调用序列化.
延迟载入DLL.一个延迟载入的DLL是隐式链接的,系统一开始不会将该DLL载入,只有当我们的代码试图去引用DLL中包含的一个符号时,系统才会实际载入该DLL.
如果应用程序使用了多个DLL,那么它的初始化可能会比较慢,因为加载程序要将所有必需的DLL映射到进程的地址空间中.缓解这个问题的一种方法是将DLL的载入过程延伸到进程的执行过程中.延迟载入DLL可以让我们很容易地实现这一点.
函数转发器:
#pragma comment(linker,“/export:SomeFunc=DllWork.SomeOtherFunc”)
这个pragma告诉链接器,正在编译的DLL应该输出一个名为SomeFunc的函数,但实际实现SomeFunc的是另一个名为SomeOtherFunc的函数,该函数被包含在另一个名为DllWork.dll的模块中.我们必须为每个想要转发的函数单独创建一行pragma.
如果延迟载入dll的话,那么在可执行模块中嵌入一个新的延迟载入段(即Delay Import Section,称为.didata)来表示要从延迟载入的dll中载入哪些函数.
每个可执行文件和DLL模块都有一个首选基地址,它表示在将模块映射到进程的地址空间中时的最佳内在地址.当我们在构建一个可执行模块的时候,链接器会将模块的首选基地址设为0x00400000. 对DLL模块来说,链接器会将首选基地址设为0x10000000.
当链接器在构建我们的模块时,会将重定位段(relocation section)嵌入到生成的文件中.
如果加载程序无法将模块载入到它的首选基地址,那么系统会打开模块的重定位段并遍历其中所有的条目.对每一个条目,加载程序会先找到包含机器指令的那个存储页面,然后将模块的首选基地址与模块的实际映射地址之间的差值,加到机器指令当前正在使用的内存地址上.
当一个模块无法被载入到它的首选基地址时,存在以下两个主要的缺点.
1. 加载程序必须遍历重定位段并修改模块中大量的代码.这个过程不仅是一大性能杀手,而且也确实会损害应用程序的初始化时间.
2. 当加载程序写入到模块的代码页面中时,系统的写时复制机制会强制这些页面以系统的页交换文件为后备存储器.
动态TLS:
DWORD TlsAlloc();
BOOL TlsSetValue(DWORD dwTlsIndex, PVOID pvTlsValue);
PVOID TlsGetValue(DWORD dwTlsIndex);
BOOL TlsFree(DWORD dwTlsIndex);
静态TLS:
__declspec(thread) DWORD gt_dwStartTime = 0;
__declspec(thread)后面的变量必须被声明为全局变量或静态变量.
当编译器对程序进行编译的时候,会将所有TLS变量放到它们自己的段中,这个段名为.tls。链接器会将所有对象模块中的.tls段合并成一个大的.tls段,并将它保存到生成的可执行文件或DLL文件中。