您的位置:首页 > 编程语言 > C语言/C++

VC++动态链接库(DLL)编程深入浅出(二)(中)

2010-12-15 14:00 639 查看
4.4 DllMain函数

  Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。

  根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

(应该是LoadLibrary的时候自动调用该入口函数)

  我们来看一个DllMain函数的例子(单击此处下载本工程附件)。

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


  DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch语句列出。

 来仔细解读一下DllMain的函数头BOOL APIENTRY DllMain( HANDLE hModule, WORD
ul_reason_for_call, LPVOID lpReserved )。

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

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

  执行下列代码:

hDll = LoadLibrary("..DebugdllTest.dll");
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));
//MAKEINTRESOURCE直接使用导出文件中的序号
if (addFun != NULL)
{
int result = addFun(2, 3);
printf(" call 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


  4.5 __stdcall约定

如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为__stdcall方式,WINAPI都采用这种方式,而C/C++缺省的调用方式却为__cdecl。__stdcall方式与__cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern
"C"),__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);

  若在lib.h中将函数声明为__stdcall调用,而应用工程中仍使用typedef int (*
lpAddFun)(int,int),运行时将发生错误(因为类型不匹配,在应用工程中仍然是缺省的__cdecl调用),弹出如图7所示的对话框。



  图7 调用约定不匹配时的运行错误

  图8中的那段话实际上已经给出了错误的原因,即“This is usually a result of …”。

  单击此处下载__stdcall调用例子工程源代码附件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: