Learn DLL (Two)

Learn DLL

6.  DLL 入口点函数

DLL入口点函数框架大致如下:

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {

 

   switch (fdwReason) {

      case DLL_PROCESS_ATTACH:

         // The DLL is being mapped into the process' 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' address space.

         break;

   }

   return(TRUE); // Used only for DLL_PROCESS_ATTACH

}

其中,hInstDll为该DLL模块的实例句柄,标识其虚存地址;fImpLoad指示模块的加载方式,非0值表示隐式加载,0表示显式加载;fdwReason指示执行DllMain函数的原因,值为switch4case值之一。

注意:

DllMain仅仅是用来作基本的初始化工作,如设置线程存储、创建内核对象、打开文件等,应避免进行如下调用:

(1)       调用其它的DLL的函数

(2)       使用LoadLibrary(Ex)加载其它DLL

(3)       调用ShellODBCCOMRPCSocket

在创建全局或static C++对象时也会面临同样的问题。

 

关于fdwReason的几个值

1DLL_PROCESS_ATTACH

DLL首次映射到进程的地址空间时,系统会调用DLLDllMain函数,fdwReason设置为DLL_PROCESS_ATTACH。在处理DLL_PROCESS_ATTACH通知时,要完成进程相关的初始化工作,例如DLL含有一个函数,它使用自己的堆(在进程地址空间中创建),那么就可以调用HeapCreate完成Heap创建工作,创建完成后可以保存在一个全局变量中。

完成处理DLL_PROCESS_ATTACH通知后,要有一个返回值表明是否初始化成功;处理其它通知时系统会忽略返回值。

DllMain总是要由某个线程来执行的,在此顺便说一下进程的创建及启动过程。在新进程创建时,系统首先为进城分配地址空间,随后将exe文件映像及需要的DLL(隐式)映射进地址空间;接下来创建主线程,使用该主线程去调用每一个DllMain完成初始化,任何一个DllMain函数返回FALSE将导致进程终止;如果所有DllMain都返回TRUE,之后主线程会调用可执行模块的C/C++运行时启动代码,最后执行应用程序的入口点函数(_tmain_tWinMain)。

如果是显式加载DLL,过程与前面类似,只是调用DllMain的线程是调用LoadLibrary(Ex)的线程,而不一定是主线程;另外在LoadLibrary(Ex)返回之后,线程继续顺序执行后面的代码。

注意:在此处可以调用DisableThreadLibraryCalls(hInstDll)禁止DllMain处理线程相关的通知。

2DLL_THREAD_ATTACH

在进程创建新线程时,该线程必须以DLL_THREAD_ATTACH通知调用所有已映射进进程地址空间的DLLDllMain,完成之后才可以执行线程函数。

 

3DLL_THREAD_DETACH

线程结束的最好方式是在线程函数内返回,在这种情况下,系统并不是马上结束线程,而是要求线程以DLL_THREAD_DETACH通知执行所有已映射进进程地址空间的DLLDllMain,完成必要的清理工作之后系统才会结束线程。

注意,调用TerminateThread 结束线程不会执行DllMain,有些清理工作不能完成,可能会造成数据丢失。DLL还可以防止线程结束,只要在此分支下安排一个无限循环就可以了,因为系统只有在所有DLLMain处理完DLL_THREAD_DETACH通知时才会结束线程。

另外,在DLL从进程空间卸载时,如果有任何线程仍在执行,系统不会为线程调用DllMain做线程相关的清理工作,这可能导致数据丢失。因此可以在DLL_PROCESS_DETACH中检测这一情况,以保证线程相关的清理工作得以执行。

(4)   DLL_PROCESS_DETACH

DLL从进程地址空间卸载时,处理DLL_PROCESS_DETACH通知,完成进程相关的清理工作。

 

DllMain函数的串行调用

要理解什么是串行调用,先看下面的例子:

有一个进程,它有两个线程AB,还引用了一个DLL——SomeDLL.dll;现在A要创建一个新线程CB要创建新线程DDllMain代码如下:

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {

 

   HANDLE hThread;

   DWORD dwThreadId;

 

   switch (fdwReason) {

   case DLL_PROCESS_ATTACH:

      // The DLL is being mapped into the process' address space.

 

      // Create a thread to do some stuff.

      hThread = CreateThread(NULL, 0, SomeFunction, NULL,

         0, &dwThreadId);

 

      // Suspend our thread until the new thread terminates.

      WaitForSingleObject(hThread, INFINITE);

 

      // We no longer need access to the new thread.

      CloseHandle(hThread);

      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' address space.

      break;

   }

   return(TRUE);

}

 

上面的代码将导致死锁,原因就是DllMain是串行调用的。只有当一个线程对DllMain的调用完成后,系统才允许别的线程进入该函数。而在例子的代码中,调用CreateThread之后线程挂起,等待新创建线程结束;而被创建的新线程在创建之前要先处理DllMain,但是它又必须等待创建自己的线程结束对DllMain的调用之后才能进入,从而新线程实际并没有创建完成,更不用提运行结束,这就形成了死锁条件——互相等待。

 

2.       DLL 符号引用

此处主要说明显示加载DLL时的符号引用,注意红色代码。

// A simple program that uses LoadLibrary and

// GetProcAddress to access myPuts from Myputs.dll.

 

#include <windows.h>

#include <stdio.h>

 

typedef int (__cdecl *MYPROC)(LPWSTR); //定义要引用的函数指针类型

 

VOID main(VOID)

{

    HINSTANCE hinstLib;

    MYPROC ProcAdd; //定义函数指针,用于保存取得的DLL函数地址

    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

 

    // Get a handle to the DLL module.

 

    hinstLib = LoadLibrary(TEXT("MyPuts.dll"));

 

    // If the handle is valid, try to get the function address.

 

    if (hinstLib != NULL)

    {

        //取得要引用的DLL函数

ProcAdd = (MYPROC) GetProcAddress(hinstLib, "myPuts");

 

        // If the function address is valid, call the function.

 

        if (NULL != ProcAdd)

        {

            fRunTimeLinkSuccess = TRUE;

            (ProcAdd) (L"Message sent to the DLL function/n"); //使用函数

        }

        // Free the DLL module.

 

        fFreeResult = FreeLibrary(hinstLib);

    }

 

    // If unable to call the DLL function, use an alternative.

    if (! fRunTimeLinkSuccess)

        printf("Message printed from executable/n");

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值