dllMain函数的作用

1 DLLMain()函数的功能

Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、Win32程序需要WinMain函数一样。根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

注:一些例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。

2 使用示例

 

 我们来看一个DllMain函数的例子

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
  case DLL_PROCESS_ATTACH:
   printf("\nprocess attach of dll");
   break;
  case DLL_THREAD_ATTACH:
   printf("\nthread attach of dll");
   break;
  case DLL_THREAD_DETACH:
   printf("\nthread detach of dll");
   break;
  case DLL_PROCESS_DETACH:
   printf("\nprocess detach of dll");
   break;
 }
 return TRUE;
}

 

3  参数含义

DllMain的函数头定义为:

BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )

其中,APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;

函数参数hModule是进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识(句柄代表了DLL模块在进程虚拟空间中的起始地址只有在特定的进程内部有效。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用。

参数ul_reason_for_call指明了被调用的原因。共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch语句列出。

lpReserved 表示一个保留参数,目前已经很少使用

 

4 调用方式

 

DllMain函数在以下几种情况被调用:

1) DLL被加载

    一个程序要调用Dll里的函数,首先要先把DLL文件映射到进程的地址空间。要把一个DLL文件映射到进程的地址空间,有两种方法:静态链接和动态链接的LoadLibrary或者LoadLibraryEx。

    当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH。这种调用只会发生在第一次映射时。如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或者LoadLibraryEx,操作系统不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数,而是增加DLL的使用次数。不同进程用LoadLibrary同一个DLL时,每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

2) DLL被卸载

 当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的fdwReason值是DLL_PROCESS_DETACH。当DLL处理该值时,它应该执行进程相关的清理工作DLL被从进程的地址空间解除映射的情况有两种:

   ① FreeLibrary()被调用。(有几个LoadLibrary,就要有几个FreeLibrary)

   ② 进程结束。在进程结束前还没有解除DLL的映射,进程结束后会解除DLL映射。(如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。)

       注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保没有清理那些没有成功初始化的东西

 

3) 单个线程启动

 

当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。新创建的线程负责执行这次的DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。

注意跟DLL_PROCESS_ATTACH的区别,我们在前面说过,第n(n>=2)次以后地把DLL映像文件映射到进程的地址空间时,只增加使用次数,而不用DLL_PROCESS_ATTACH调用DllMain。DLL_THREAD_ATTACH不同,进程中的每次建立线程,都会用值DLL_THREAD_ATTACH调用DllMain函数,哪怕是线程中建立线程也一样。

 

4) 单个线程终止

 

如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。

注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread(),系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

 

 

5 注意事项

__stdcall约定

为了使VC++编写的DLL能被其他语言编写的程序调用, 函数的调用方式应显式声明为__stdcall方式,因为在C/C++中,缺省的调用方式为__cdecl。

__stdcall方式与__cdecl调用方式的区别在于会将函数名处理成不同的符号。具体如下:

__stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number;

__cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

Windows编程中常见的几种函数类型声明宏都是与__stdcall和__cdecl有关的(节选自windef.h):

#define CALLBACK __stdcall //这就是传说中的回调函数
#define WINAPI __stdcall //这就是传说中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在这里
#define APIPRIVATE __stdcall
#define PASCAL __stdcall

 

在lib.h中,应这样声明add函数:

int __stdcall add(int x, int y);


在应用工程中函数指针类型应定义为:

typedef int(__stdcall *lpAddFun)(int, int);

 

 


  执行下列代码:

hDll = LoadLibrary("text.dll");
if (hDll != NULL)
{
 addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
 //MAKEINTRESOURCE直接使用导出文件中的序号
 if (addFun != NULL)
 {
  int result = addFun(2, 3);
  printf("\ncall add in dll:%d", result);
 }
 FreeLibrary(hDll);
}


我们看到输出顺序为:

process attach of dll
call add in dll:5
process detach of dll


这一输出顺序验证了DllMain被调用的时机。

  代码中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通过.def文件中为add函数指定的顺序号访问add函数,具体体现在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义为(节选自winuser.h):

#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Windows 平台上,`DllMain` 函数DLL入口点,它在 DLL 被加载或卸载时被调用。通常情况下,`DllMain` 函数是由操作系统自动调用的,无需手动调用。 如果你想在 DLL 加载时执行一些初始化代码,可以在 `DllMain` 函数中实现。如果你需要在 DLL 卸载时执行一些清理代码,也可以在 `DllMain` 函数中实现。 以下是一个简单的 `DllMain` 函数示例: ```c++ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // DLL 加载时执行的代码 break; case DLL_THREAD_ATTACH: // 新线程创建时执行的代码 break; case DLL_THREAD_DETACH: // 线程结束时执行的代码 break; case DLL_PROCESS_DETACH: // DLL 卸载时执行的代码 break; } return TRUE; } ``` 在以上代码中,`ul_reason_for_call` 参数表示 DLL 的加载和卸载原因。`DLL_PROCESS_ATTACH` 表示 DLL 加载,`DLL_PROCESS_DETACH` 表示 DLL 卸载。 如果你需要手动调用 `DllMain` 函数,可以使用 `LoadLibrary` 和 `FreeLibrary` 函数。`LoadLibrary` 函数可以加载 DLL 文件,并返回 DLL 模块的句柄。`FreeLibrary` 函数可以卸载 DLL 并释放相关资源。 以下是一个手动调用 `DllMain` 函数的示例: ```c++ HMODULE hModule = LoadLibrary("mydll.dll"); if (hModule != NULL) { typedef BOOL (*PDLLMAIN)(HMODULE, DWORD, LPVOID); PDLLMAIN pfnDllMain = (PDLLMAIN)GetProcAddress(hModule, "DllMain"); if (pfnDllMain != NULL) { // 调用 DllMain 函数 pfnDllMain(hModule, DLL_PROCESS_ATTACH, NULL); } FreeLibrary(hModule); } ``` 在以上代码中,`GetProcAddress` 函数可以获取 DLL 模块中导出函数的地址。`PDLLMAIN` 是一个函数指针类型,它指向 `DllMain` 函数。通过调用函数指针,可以手动调用 `DllMain` 函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值