DLL的进入点函数
一个D L L可以拥有单个进入点函数。系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。这些调用可以用来提供一些信息,通常用于供D L L进行每个进程或线程的初始化和清除操作。如果你的D L L不需要这些通知信息,就不必在D L L源代码中实现这个函数。例如,如果你创建一个只包含资源的D L L,就不必实现该函数。如果确实需要在D L L中接受通知信息,可以实现类似下面的进入点函数:
BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad) { switch(fdwReason) { case DLL_PROCESS_ATTACH: //The DLL is being mapped into the process's address space. break; case DLL_THREAD_ATTACH: //A thread is being created. break; case DLL_THREAD_DETACH: //A thread is exiting cleanly. break; case DLL_PROCESS_DETACH: //The DLL is being unmapped from the process's address space. break; } return(TRUE); // Used only for DLL_PROCESS_ATTACH }
注意函数名D l l M a i n是区分大小写的。
参数h i n s t D l l包含了D L L的实例句柄。与( w ) Wi n M a i n函数的h i n s t E x e参数一样,这个值用于标识D L L的文件映像被映射到进程的地址空间中的虚拟内存地址。通常应将这个参数保存在一个全局变量中,这样就可以在调用加载资源的函数(如D i a l o g B o x和L o a d S t r i n g)时使用它。最后一个参数是f I m p L o a d,如果D L L是隐含加载的,那么该参数将是个非0值,如果D L L是显式加载的,那么它的值是0。
参数f d w R e a s o n用于指明系统为什么调用该函数。该参数可以使用4个值中的一个。这4个值是: D L L _ P R O C E S S _ AT TA C H、D L L _ P R O C E S S _ D E TA C H、D L L _ T H R E A D _ AT TA C H或D L L _ T H R E A D _ D E TA C H。这些值将在下面介绍。
注意必须记住,D L L使用D l l M a i n函数来对它们进行初始化。当你的D l l M a i n函数执行时,同一个地址空间中的其他D L L可能尚未执行它们的D l l M a i n函数。这意味着它们尚未初始化,因此你应该避免调用从其他D L L中输入的函数。此外,你应该避免从D l l M a i n内部调用L o a d L i b r a r y ( E x )和F r e e L i b r a r y函数,因为这些函数会形式一个依赖性循环。
DLL_PROCESS_ATTACH通知
当D L L被初次映射到进程的地址空间中时,系统将调用该D L L的D l l M a i n函数,给它传递参数f d w R e a s o n的值D L L _ P R O C E S S _ AT TA C H。只有当D L L的文件映像初次被映射时,才会出现这种情况。如果线程在后来为已经映射到进程的地址空间中的D L L调用L o a d L i b r a r y ( E x )函数,那么操作系统只是递增D L L的使用计数,它并不再次用D L L _ P R O C E S S _ AT TA C H的值来调用D L L的D l l M a i n函数。
当处理D L L _ P R O C E S S _ AT TA C H时,D L L应该执行D L L中的函数要求的任何与进程相关的初始化。例如, D L L可能包含需要使用它们自己的堆栈(在进程的地址空间中创建)的函数。
DLL_PROCESS_DETACH通知
D L L从进程的地址空间中被卸载时,系统将调用D L L的D l l M a i n函数,给它传递f d w R e a s o n的值D L L _ P R O C E S S _ D E TA C H。当D L L处理这个值时,它应该执行任何与进程相关的清除操作。例如, D L L可以调用H e a p D e s t r o y函数来撤消它在D L L _ P R O C E S S _ D E TA C H通知期间创建的堆栈。
DLL_THREAD_ATTACH通知
当在一个进程中创建线程时,系统要查看当前映射到该进程的地址空间中的所有D L L文件映像,并调用每个文件映像的带有D L L _ T H R E A D _ AT TA C H值的D l l M a i n函数。这可以告诉所有的D L L执行每个线程的初始化操作。新创建的线程负责执行D L L的所有D l l M a i n函数中的代码。只有当所有的D L L都有机会处理该通知时,系统才允许新线程开始执行它的线程函数。
DLL_THREAD_DETACH通知
让线程终止运行的首选方法是使它的线程函数返回。这使得系统可以调用E x i t T h r e a d来撤消该线程。E x i t T h r e a d函数告诉系统,该线程想要终止运行,但是系统并不立即将它撤消。相反, 它要取出这个即将被撤消的线程, 并让它调用已经映射的D L L 的所有带有D L L _ T H R E A D _ D E TACH 值的D l l M a i n函数。这个通知告诉所有的D L L执行每个线程的清除操作。
DllMain与C/C++运行期库
当编写一个D L L时,你需要得到C / C + +运行期库的某些初始帮助。例如,如果你创建的D L L包含一个全局变量,而这个全局变量是个C + +类的实例。在你顺利地在D l l M a i n函数中使用这个全局变量之前,该变量必须调用它的构造函数。这是由C / C + +运行期库的D L L启动代码来完成的。当你的D L L文件映像被映射到进程的地址空间中时,系统实际上是调用_ D l l M a i n C RTS t a r t u p函数,而不是调用D l l M a i n函数。
延迟加载DLL (但是怎么延迟那?^_^)
Microsoft Visual C++ 6.0提供了一个出色的新特性,它能够使D L L的操作变得更加容易。这个特性称为延迟加载D L L。延迟加载的D L L是个隐含链接的D L L,它实际上要等到你的代码试图引用D L L中包含的一个符号时才进行加载。延迟加载的D L L在下列情况下是非常有用的:
• 如果你的应用程序使用若干个D L L,那么它的初始化时间就比较长,因为加载程序要将所有需要的D L L映射到进程的地址空间中。解决这个问题的方法之一是在进程运行的时候分开加载各个D L L。延迟加载的D L L能够更容易地完成这样的加载。
• 如果调用代码中的一个新函数,然后试图在老版本的系统上运行你的应用程序,而该系统中没有该函数,那么加载程序就会报告一个错误,并且不允许该应用程序运行。你需要一种方法让你的应用程序运行,然后,如果(在运行时)发现该应用程序在老的系统上运行,那么你将不调用遗漏的函数。
函数转发器
函数转发器是D L L的输出节中的一个项目,用于将对一个函数的调用转至另一个D L L中的另一个函数。
DLL转移
M i c r o s o f t给Windows 2000增加了一个D L L转移特性。这个特性能够强制操作系统的加载程序首先从你的应用程序目录中加载文件模块。只有当加载程序无法在应用程序目录中找到该文件时,它才搜索其他目录。为了强制加载程序总是首先查找应用程序的目录,要做的工作就是在应用程序的目录中放入一个文件。该文件的内容可以忽略,但是该文件必须称为A p p N a m e . l o c a l。例如,如果有一个可执行文件的名字是S u p e r A p p . e x e ,那么转移文件必须称为S u p e r A p p . e x e . l o c a l。在系统内部, L o a d L i b r a r y ( E x )已经被修改,以便查看是否存在该文件。如果应用程序的目录中存在该文件,该目录中的模块就已经被加载。如果应用程序的目录中不存在这个模块,L o a d L i b r a r y ( E x )将正常运行。对于已经注册的C O M对象来说,这个特性是非常有用的。它使应用程序能够将它的C O M对象D L L放入自己的目录,这样,注册了相同C O M对象的其他应用程序就无法干扰你的操作。
zz