该系列文章是依据本人平时对动态链接库的学习,归纳总结,所做的学习笔记。如有错误或待改善之处,请留下您宝贵的意见或建议。
以下讲的是DLL的DllMain。
Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。
根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。
进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识进程自己还有一个HINSTANCE句柄。所有这些模块句柄都只有在特定的进程内部有效,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用。进程模块句柄几乎总是等于0x400000,而DLL模块的加载地址的缺省句柄是0x10000000。如果程序同时使用了几个DLL模块,每一个都会有不同的HINSTANCE值。这是因为在创建DLL文件时指定了不同的基地址,或者是因为加载程序对DLL代码进行了重定位。
以下是一个DllMain的例子:
#include "lib.h"
#include "windows.h"
#include "stdio.h"
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;
}
int add(int x,int y)
{
return x + y;
}
DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch语句列出。
来仔细解读一下DllMain的函数头BOOL APIENTRYDllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。
APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式。
执行的代码如下:
#include "stdafx.h"
#include "windows.h"
typedef int (* lpAddFun)(int,int);
int main(int argc, char* argv[])
{
HINSTANCE hDll;
lpAddFun addFun;
hDll = LoadLibrary("..\\Debug\\dllTest.dll");
if (hDll != NULL)
{
//addFun = (lpAddFun)GetProcAddress(hDll,"add");
addFun = (lpAddFun)GetProcAddress(hDll,MAKEINTRESOURCE(1));
//MAKEINTRESOURCE直接使用导出文件中的序号
if(addFun!=NULL)
{
int result = addFun(2,3);
printf("\ncall add in dll:%d",result);
}
FreeLibrary(hDll);
}
getchar();
return 0;
}
与上一节一样,也是在同一个工作区中建立两个工程。
代码中的GetProcAddress (hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通过.def文件中为add函数指定的顺序号访问add函数,具体体现在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义在winuser.h中。