《Win32多线程程序设计》学习笔记 第14章 建造DLL

DLL的通告消息Notifications

 任何时候,当一个进程载入或卸载一个dll时,dllmain会被调用。线程也是一样的。当一个进程开始执行时,它所用到的每个dll的dllmain都会被系统调用之,并获得一个DLL_PROCESS_ATTACH消息。如果是线程开始执行,进程所用到的每一个dll的dllmain也都会被系统调用之,并获得DLL_THREAD_ATTACH消息。

BOOL WINAPI DllMain(
  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

BOOL WINAPI DisableThreadLibraryCalls(
  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数组中配置一个槽,并传回其数组索引。每个线程可以自己拥有一份槽内容的拷贝,但是所有拷贝都必须使用同一槽索引。

DWORD WINAPI TlsAlloc(void);

返回值:如果成功,传回一个TLS数组中的一个槽。

BOOL WINAPI TlsSetValue(
    DWORD dwTlsIndex,    
//由TlsAlloc传回的TLS槽索引
    LPVOID lpTlsValue    //要储存到槽中的数据值
);

返回值:成功返回TRUE
LPVOID WINAPI TlsGetValue(
  __in          DWORD dwTlsIndex   
//槽索引
);

返回值:成功,传回存在槽中的值。失败返回0

BOOL WINAPI TlsFree(
  __in          DWORD dwTlsIndex
);

//成功返回TRUE

一个TLS索引只在同一个进程中才有意义。

 _declspec(thread)

 将一个变量或结构声明为“具有线程局部性”。

  • 如果一个对象又有构造函数或者析构函数,就不能够被声明为_declspec(thread).因此,你必须在线程启动时手动初始化对象。
  •  一个Dll如果使用了_declspec(thread),就没有办法被LoadLibrary载入。

 数据一致性

  •  不要使用全局变量,用来储存槽着除外
  • 不要是用静态变量。
  • 尽量使用TLS
  • 尽量使用你的堆栈
原文地址:https://www.cnblogs.com/kwliu/p/2195900.html