《Win32多线程程序设计》学习笔记 第14章 建造DLL
DLL的通告消息Notifications
任何时候,当一个进程载入或卸载一个dll时,dllmain会被调用。线程也是一样的。当一个进程开始执行时,它所用到的每个dll的dllmain都会被系统调用之,并获得一个DLL_PROCESS_ATTACH消息。如果是线程开始执行,进程所用到的每一个dll的dllmain也都会被系统调用之,并获得DLL_THREAD_ATTACH消息。
HINSTANCE hinstDLL, //这个dll的module handle
DWORD fdwReason, //dllmain被调用的原因。可能是以下之一:DLL_PROCESS_ATTACH;DLL_THREAD_ATTACH;DLL_THREAD_DETACH;DLL_PROCESS_DETACH
LPVOID lpvReserved //提供更多信息补充fdwReason,如果fdwReason是DLL_PROCESS_ATTACH,那么lpvReserved 为NULL表示dll是被loadlibrary载入的,否则是隐式载入
);
返回值:如果fdwReason是DLL_PROCESS_ATTACH,那么dllmain应该在成功时传回TRUE,在失败时传回FALSE。如果不是,那么返回值被忽略。
Win32 中,每个程序的第一个线程调用dllmain时,是以dll_process_attach调用,后续的线程才是以dll_thread_attach调用。dllmain是在新线程的context中被调用。因为你需要一个context,才能够使用线程局部存储(TLS)。
如果要加载的dll很多时,由于dllmain的调用,会加大很多负担,所以可以使用如下函数禁用不需要通告消息的dlls
HMODULE hModule //dll的module handle
);
返回:如果成功,返回TRUE。如果所指定的dll使用了线程局部存储,这个函数调用一定会失败。
当一个dll被loadlibrary或者loadlibraryex动态载入时,dllmain不会收到任何正在执行的线程的dll_THREAD_ATTACH通告消息,但有一个线程除外,就是调用loadlibrary的那个。
如果DllMain收到DLL_PROCESS_ATTACH时因不能正确初始化而传回FALSE,DllMain还是会收到DLL_PROCESS_DETACH。
- 如果进程调用LoadLibrary时,有一个以上的线程正在运行,那么DLL_THREAD_ATTACH不会针对每一个线程送出。只有调用LoadLibrary的那个线程才发出。
- DllMain不接受第一个线程的DLL_THREAD_ATTACH,而已DLL_PROCESS_ATTACH代之
- DllMain不接受任何因TerminateThread而结束之线程的DLL_TREAD_DETACH通告消息。如果程序调用exit(1)或exitProcess结束自己,这种情况会发生。
DLL进入点的依序执行(Serialization)特性
在一个进程之中,一次只能有一个线程执行一个DLL的DllMain函数。每个线程依次调用每个附着的Dlls的DllMain函数。
MFC中的Dll通告消息
一个使用MFC的Dll,拥有它自己的CWinThread对象,当Dll接受到DLL_PROCESS_ATTACH时,MFC会调用CWinThread::InitInstance。当Dll收到DLL_PROCESS_DETACH时,调用CWinThread::ExitInstance函数,另外2个消息,没有虚函数被调用。
喂食给Worker线程
我们在一个worker线程中调用GetMessage之类的函数时,系统就会给该线程产生一个消息队列。这样我们就可以使用消息来和这个worker线程通信了。
线程局部存储(TLS)
TLS是一种机制,通过它,线程可以持有一个指针,指向它自己的一份数据结构拷贝。MFC使用TLS来追踪每个线程所使用的GDI对象和USER对象。
线程局部存储的运作方式是,每个线程有一个由4字节槽所组成的数组。 这个数组保证至少有TLS_MINIMUM_AVAILABLE个槽在其中。至少64.每个槽可被指定放置特殊结构。
所有被配置的内存 ,甚至是dll所配置的内存,都是在调用端进程的context 中配置的。你的Dll会获得每一个调用端进程的所有全局变量的一份拷贝。
TLS的使用起始于TlsAlloc。TlsAlloc()在TLS数组中配置一个槽,并传回其数组索引。每个线程可以自己拥有一份槽内容的拷贝,但是所有拷贝都必须使用同一槽索引。
返回值:如果成功,传回一个TLS数组中的一个槽。
DWORD dwTlsIndex, //由TlsAlloc传回的TLS槽索引
LPVOID lpTlsValue //要储存到槽中的数据值
);
返回值:成功返回TRUE
__in DWORD dwTlsIndex //槽索引
);
返回值:成功,传回存在槽中的值。失败返回0
BOOL WINAPI TlsFree(
__in DWORD dwTlsIndex
);
//成功返回TRUE
一个TLS索引只在同一个进程中才有意义。
_declspec(thread)
将一个变量或结构声明为“具有线程局部性”。
- 如果一个对象又有构造函数或者析构函数,就不能够被声明为_declspec(thread).因此,你必须在线程启动时手动初始化对象。
- 一个Dll如果使用了_declspec(thread),就没有办法被LoadLibrary载入。
数据一致性
- 不要使用全局变量,用来储存槽着除外
- 不要是用静态变量。
- 尽量使用TLS
- 尽量使用你的堆栈