20DLL的高级操作技术文档格式.docx

上传人:b****6 文档编号:17897502 上传时间:2022-12-12 格式:DOCX 页数:44 大小:430.88KB
下载 相关 举报
20DLL的高级操作技术文档格式.docx_第1页
第1页 / 共44页
20DLL的高级操作技术文档格式.docx_第2页
第2页 / 共44页
20DLL的高级操作技术文档格式.docx_第3页
第3页 / 共44页
20DLL的高级操作技术文档格式.docx_第4页
第4页 / 共44页
20DLL的高级操作技术文档格式.docx_第5页
第5页 / 共44页
点击查看更多>>
下载资源
资源描述

20DLL的高级操作技术文档格式.docx

《20DLL的高级操作技术文档格式.docx》由会员分享,可在线阅读,更多相关《20DLL的高级操作技术文档格式.docx(44页珍藏版)》请在冰豆网上搜索。

20DLL的高级操作技术文档格式.docx

9)链接程序将各个.obj模块链接起来,生成.exe文件。

注:

DLL的lib文件是不需要的,因为并不直接引用输出符号。

.exe文件不包含输入表。

运行应用程序:

10)加载程序为.exe创建模块地址空进程的主线程开始执行;

应用程序启动运行。

显式加载DLL:

11)一个线程调用LoadLibrary(Ex)函数,将DLL加载到进程

的地址空间这时线程可以调用GetProcAddress以便间接

引用DLL的输出符号。

图20-1应用程序创建和显式链接DLL的示意图

第二种方法是在应用程序运行时让应用程序显式加载需要的DLL并且显式链接到需要的输出符号。

换句话说,当应用程序运行时,它里面的线程能够决定它是否要调用DLL中的函数。

该线程可以将DLL显式加载到进程的地址空间,获得DLL中包含的函数的虚拟内存地址,然后使用该内存地址调用该函数。

这种方法的优点是一切操作都是在应用程序运行时进行的。

图20-1显示了一个应用程序是如何显式地加载DLL并且链接到它里面的符号的。

20.1.1显式加载DLL模块

无论何时,进程中的线程都可以决定将一个DLL映射到进程的地址空间,方法是调用下面两个函数中的一个:

HINSTANCELoadLibrary(PCTSTRpszDLLPathName);

HINSTANCELoadLibraryEx(

PCTSTRpszDLLPathName,

HANDLEhFile,

DWORDdwFlags);

这两个函数均用于找出用户系统上的文件映像(使用上一章中介绍的搜索算法),并设法将DLL的文件映像映射到调用进程的地址空间中。

两个函数返回的HINSTANCE值用于标识文件映像映射到的虚拟内存地址。

如果DLL不能被映射到进程的地址空间,则返回NULL。

若要了解关于错误的详细信息,可以调用GetLastError.

你会注意到,LoadLibraryEx函数配有两个辅助参数,即hFile和dwFlags。

参数hFile保留供将来使用,现在必须是NULL。

对于参数dwFlags,必须将它设置为0,或者设置为DONT_RESOLVE_DLL_REFERENCES、LOAD_LIBRARY_AS_DATAFILE和LOAD_WITH_ALTERED_SEARCH_PATH等标志的一个组合。

1.DONT_RESOLVE_DLL_REFERENCES

DONT_RESOLVE_DLL_REFERENCES标志用于告诉系统将DLL映射到调用进程的地址空间中。

通常情况下,当DLL被映射到进程的地址空间中时,系统要调用DLL中的一个特殊函数,即DllMain(本章后面介绍)。

该函数用于对DLL进行初始化。

DONT_RESOLVE_DLL_REFERENCES标志使系统不必调用DllMain函数就能映射文件映像。

此外,DLL能够输入另一个DLL中包含的函数。

当系统将一个DLL映射到进程的地址空间中时,它也要查看该DLL是否需要其他的DLL,并且自动加载这些DLL。

当DONT_RESOLVE_DLL_REFERENCES标志被设定时,系统并不自动将其他的DLL加载到进程的地址空间中。

2.LOAD_LIBRARY_AS_DATAFILE

LOAD_LIBRARY_AS_DATAFILE标志与DONT_RESOLVE_DLL_REFERENCES标志相类似,因为系统只是将DLL映射到进程的地址空间中,就像它是数据文件一样。

系统并不花费额外的时间来准备执行文件中的任何代码。

例如,当一个DLL被映射到进程的地址空间中时,系统要查看DLL中的某些信息,以确定应该将哪些页面保护属性赋予文件的不同的节。

如果设定了LOAD_LIBRARY_AS_DATAFILE标志,系统将以它要执行文件中的代码时的同样方式来设置页面保护属性。

由于下面几个原因,该标志是非常有用的。

首先,如果有一个DLL(它只包含资源,但不包含函数),那么可以设定这个标志,使DLL的文件映像能够映射到进程的地址空间中。

然后可以在调用加载资源的函数时,使用LoadLibraryEx函数返回的HINSTANCE值。

通常情况下,加载一个.exe文件,就能够启动一个新进程,但是也可以使用LoadLibraryEx函数将.exe文件的映像映射到进程的地址空间中。

借助映射的.exe文件的HINSTANCE值,就能够访问文件中的资源。

由于.exe文件没有DllMain函数,因此,当调用LoadLibraryEx来加载一个.exe文件时,必须设定LOAD_LIBRARY_AS_DATAFILE标志。

3.LOAD_WITH_ALTERED_SEARCH_PATH

LOAD_WITH_ALTERED_SEARCH_PATH标志用于改变LoadLibraryEx用来查找特定的DLL文件时使用的搜索算法。

通常情况下,LoadLibraryEx按照第19章讲述的顺序进行文件的搜索。

但是,如果设定了LOAD_WITH_ALTERED_SEARCH_PATH标志,那么LoadLibraryEx函数就按照下面的顺序来搜索文件:

1)pszDLLPathName参数中设定的目录。

 

2)进程的当前目录。

3)Windows的系统目录。

4)Windows目录。

5)PATH环境变量中列出的目录。

20.1.2显式卸载DLL模块

当进程中的线程不再需要DLL中的引用符号时,可以从进程的地址空间中显式卸载DLL,方法是调用下面的函数:

BOOLFreeLibrary(HINSTANCEhinstDll);

必须传递HINSTANCE值,以便标识要卸载的DLL。

该值是较早的时候调用LoadLibrary(Ex)而返回的值。

也可以通过调用下面的函数从进程的地址空间中卸载DLL:

VOIDFreeLibraryAndExitThread(

HINSTANCEhinstDll,

DWORDdwExitCode);

该函数是在Kernel32.dll中实现的,如下所示:

VOIDFreeLibraryAndExitThread(HINSTANCEhinstDll,DWORDdwExitCode)

{

FreeLibrary(hinstDll);

ExitThread(dwExitCode);

}

初看起来,这并不是个非常高明的代码,你可能不明白,为什么Microsoft要创建FreeLibraryAndExitThread这个函数。

其原因与下面的情况有关:

假定你要编写一个DLL,当它被初次映射到进程的地址空间中时,该DLL就创建一个线程。

当该线程完成它的操作时,它通过调用FreeLibrary函数,从进程的地址空间中卸载该DLL,并且终止运行,然后立即调用ExitThread。

但是,如果线程分开调用FreeLibrary和ExitThread,就会出现一个严重的问题。

这个问题是调用FreeLibrary会立即从进程的地址空间中卸载DLL。

当调用的FreeLibrary返回时,包含对ExitThread调用的代码就不再可以使用,因此线程将无法执行任何代码。

这将导致访问违规,同时整个进程终止运行。

但是,如果线程调用FreeLibraryAndExitThread,该函数调用FreeLibrary,使DLL立即被卸载。

下一个执行的指令是在Kernel32.dll中,而不是在刚刚被卸载的DLL中。

这意味着该线程能够继续执行,并且可以调用ExitThread。

ExitThread使该线程终止运行并且不返回。

一般来说,并没有很大的必要去调用FreeLibraryAndExitThread函数。

我曾经使用过一次,因为我执行了一个非常特殊的任务。

另外,我为MicrosoftWindows3.1编写了一个代码,它并没有提供这个函数。

因此我高兴地看到Microsoft将这个函数增加到了较新的Windows版本中。

在实际环境中,LoadLibrary和LoadLibraryEx这两个函数用于对与特定的库相关的进程使用计数进行递增,FreeLibrary和FreeLibraryAndExitThread这两个函数则用于对库的每个进程的使用计数进行递减。

例如,当第一次调用LoadLibrary函数来加载DLL时,系统将DLL的文件映像映射到调用进程的地址空间中,并将DLL的使用计数设置为1。

如果同一个进程中的线程后来调用LoadLibrary来加载同一个DLL文件映像,系统并不第二次将DLL映像文件映射到进程的地址空间中,它只是将与该进程的DLL相关的使用计数递增1。

为了从进程的地址空间中卸载DLL文件映像,进程中的线程必须两次调用FreeLibrary函数。

第一次调用只是将DLL的使用计数递减为1,第二次调用则将DLL的使用计数递减为0。

当系统发现DLL的使用计数递减为0时,它就从进程的地址空间中卸载DLL的文件映像。

试图调用DLL中的函数的任何线程都会产生访问违规,因为特定地址上的代码不再被映射到进程的地址空间中。

系统为每个进程维护了一个DLL的使用计数,也就是说,如果进程A中的一个线程调用下面的函数,然后进程B中的一个线程调用相同的函数,那么MyLib.dll将被映射到两个进程的地址空间中,这样,进程A和进程B的DLL使用计数都将是1。

HINSTANCEhinstDll=LoadLibrary("

MyLib.dll"

);

如果进程B中的线程后来调用下面的函数,那么进程B的DLL使用计数将变成0,并且该DLL将从进程B的地址空间中卸载。

但是,进程A的地址空间中的DLL映射不会受到影响,进程A的DLL使用计数仍然是1。

FreeLibrary(hinstDll);

如果调用GetModuleHandle函数,线程就能够确定DLL是否已经被映射到进程的地址空间中:

HINSTANCEGetModuleHandle(PCTSTRpszModuleName);

例如,只有当MyLib.dll尚未被映射到进程的地址空间中时,下面这个代码才能加载该文件:

HINSTANCEhinstDll=GetModuleHandle("

MyLib"

//DLLextensionassumed

if(hinstDll==NULL)

hinstDll=LoadLibrary("

//DLLextensionassumed

如果只有DLL的HINSTANCE值,那么可以调用GetModuleFileName函数,确定DLL(或.exe)的全路径名:

DWORDGetModuleFileName(

HINSTANCEhinstModule,

PTSTRpszPathName,

DWORDcchPath);

第一个参数是DLL(或.exe)的HINSTANCE。

第二个参数pszPathName是该函数将文件映像的全路径名放入的缓存的地址。

第三参数cchPath用于设定缓存的大小(以字符为计量单位)。

20.1.3显式链接到一个输出符号

一旦DLL模块被显式加载,线程就必须获取它要引用的符号的地址,方法是调用下面的函数:

FARPROCGetProcAddress(

PCSTRpszSymbolName);

参数hinstDll是调用LoadLibrary(Ex)或GetModuleHandle函数而返回的,它用于设定包含符号的DLL的句柄。

参数pszSymbolName可以采用两种形式。

第一种形式是以0结尾的字符串的地址,它包含了你想要其地址的符号的名字:

FARPROCpfn=GetProcAddress(hinstDll,"

SomeFuncInDll"

注意,参数pszSymbolName的原型是PCSTR,而不是PCTSTR。

这意味着GetProcAddress函数只接受ANSI字符串,决不能将Unicode字符串传递给该函数,因为编译器/链接程序总是将符号名作为ANSI字符串存储在DLL的输出节中。

参数pszSymbolName的第二种形式用于指明你想要其地址的符号的序号:

FARPROCpfn=GetProcAddress(hinstDll,MAKEINTRESOURCE

(2));

这种用法假设你知道你需要的符号名被DLL创建程序赋予了序号值2。

同样,我要再次强调,Microsoft非常反对使用序号,因此你不会经常看到GetProcAddress的这个用法。

这两种方法都能够提供包含在DLL中的必要符号的地址。

如果DLL模块的输出节中不存在你需要的符号,GetProcAddress就返回NULL,表示运行失败。

应该知道,调用GetProcAddress的第一种方法比第二种方法要慢,因为系统必须进行字符串的比较,并且要搜索传递的符号名字符串。

对于第二种方法来说,如果传递的序号尚未被分配给任何输出的函数,那么GetProcAddress就会返回一个非NULL值。

这个返回值将会使你的应用程序错误地认为你已经拥有一个有效的地址,而实际上你并不拥有这样的地址。

如果试图调用该地址,肯定会导致线程引发一个访问违规。

我在早期从事Windows编程时,并不完全理解这个行为特性,因此多次出现这样的错误。

所以一定要小心(这个行为特性是应该避免使用序号而使用符号名的另一个原因)。

20.2DLL的进入点函数

一个DLL可以拥有单个进入点函数。

系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。

这些调用可以用来提供一些信息,通常用于供DLL进行每个进程或线程的初始化和清除操作。

如果你的DLL不需要这些通知信息,就不必在DLL源代码中实现这个函数。

例如,如果你创建一个只包含资源的DLL,就不必实现该函数。

如果确实需要在DLL中接受通知信息,可以实现类似下面的进入点函数:

BOOLWINAPIDllMain(HINSTANCEhinstDll,DWORDfdwReason,PVOIDfImpLoad)

switch(fdwReason)

{

caseDLL_PROCESS_ATTACH:

//TheDLLisbeingmappedintotheprocess'

saddressspace.

break;

caseDLL_THREAD_ATTACH:

//Athreadisbeingcreated.

caseDLL_THREAD_DETACH:

//Athreadisexitingcleanly.

caseDLL_PROCESS_DETACH:

//TheDLLisbeingunmappedfromtheprocess'

}

return(TRUE);

//UsedonlyforDLL_PROCESS_ATTACH

注意函数名DllMain是区分大小写的。

许多编程人员有时调用的函数是DLLMain。

这是一个非常容易犯的错误,因为DLL这个词常常使用大写来表示。

如果调用的进入点函数不是DllMain,而是别的函数,你的代码将能够编译和链接,但是你的进入点函数永远不会被调用,你的DLL永远不会被初始化。

参数hinstDll包含了DLL的实例句柄。

与(w)WinMain函数的hinstExe参数一样,这个值用于标识DLL的文件映像被映射到进程的地址空间中的虚拟内存地址。

通常应将这个参数保存在一个全局变量中,这样就可以在调用加载资源的函数(如DialogBox和LoadString)时使用它。

最后一个参数是fImpLoad,如果DLL是隐含加载的,那么该参数将是个非0值,如果DLL是显式加载的,那么它的值是0。

参数fdwReason用于指明系统为什么调用该函数。

该参数可以使用4个值中的一个。

这4个值是:

DLL_PROCESS_ATTACH、DLL_PROCESS_DETACH、DLL_THREAD_ATTACH或DLL_THREAD_DETACH。

这些值将在下面介绍。

注意必须记住,DLL使用DllMain函数来对它们进行初始化。

当你的DllMain函数执行时,同一个地址空间中的其他DLL可能尚未执行它们的DllMain函数。

这意味着它们尚未初始化,因此你应该避免调用从其他DLL中输入的函数。

此外,你应该避免从DllMain内部调用LoadLibrary(Ex)和FreeLibrary函数,因为这些函数会形式一个依赖性循环。

PlatformSDK文档说,你的DllMain函数只应该进行一些简单的初始化,比如设置本地存储器(第21章介绍),创建内核对象和打开文件等。

你还必须避免调用User、Shell、ODBC、COM、RPC和套接字函数(即调用这些函数的函数),因为它们的DLL也许尚未初始化,或者这些函数可能在内部调用LoadLibrary(Ex)函数,这同样会形成一个依赖性循环。

另外,如果创建全局性的或静态的C++对象,那么应该注意可能存在同样的问题,因为在你调用DllMain函数的同时,这些对象的构造函数和析构函数也会被调用。

20.2.1DLL_PROCESS_ATTACH通知

当DLL被初次映射到进程的地址空间中时,系统将调用该DLL的DllMain函数,给它传递参数fdwReason的值DLL_PROCESS_ATTACH。

只有当DLL的文件映像初次被映射时,才会出现这种情况。

如果线程在后来为已经映射到进程的地址空间中的DLL调用LoadLibrary(Ex)函数,那么操作系统只是递增DLL的使用计数,它并不再次用DLL_PROCESS_ATTACH的值来调用DLL的DllMain函数。

当处理DLL_PROCESS_ATTACH时,DLL应该执行DLL中的函数要求的任何与进程相关的初始化。

例如,DLL可能包含需要使用它们自己的堆栈(在进程的地址空间中创建)的函数。

通过在处理DLL_PROCESS_ATTACH通知时调用HeapCreate函数,该DLL的DllMain函数就能够创建这个堆栈。

已经创建的堆栈的句柄可以保存在DLL函数有权访问的一个全局变量中。

当DllMain处理一个DLL_PROCESS_ATTACH通知时,DllMain的返回值能够

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1