DLL基础(动态链接库)

DLL基础(动态链接库)

动态链接库( D L L)一直是这个操作系统的基础。 Windows API中的所有函数都包含在 D L L中。3个最重要的 D L L是K e r n e l 3 2 . d l l,它包含用于管理内存、进程和线程的各个函数; U s e r 3 2 . d l l,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数; G D I 3 2 . d l l,它包含用于画图和显示文本的各个函数。

当编译器看到负责修改变量、函数或 C + +类的_ _ d e c l s p e c ( d l l e x p o r t )时,它就知道该变量、函数或C + +类是从产生的D L L模块输出的。如果线程需要调用D L L模块中的函数,那么D L L的文件映像必须映射到调用线程的进程地址空间中。可以用两种方法进行这项操作。第一种方法是让应用程序的源代码只引用 D L L中包含的符号。这样,当应用程序启动运行时,加载程序就能够隐含加载(和链接)需要的 D L L。
第二种方法是在应用程序运行时让应用程序显式加载需要的 D L L并且显式链接到需要的输出符号。换句话说,当应用程序运行时,它里面的线程能够决定它是否要调用 D L L中的函数。该线程可以将D L L显式加载到进程的地址空间,获得 D L L中包含的函数的虚拟内存地址,然后使用该内存地址调用该函数。这种方法的优点是一切操作都是在应用程序运行时进行的。

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

HINSTANCE LoadLibrary(PCTSTR pszDLLPathName);

HINSTANCE LoadLibraryEx(

PCTSTR pszDLLPathName,

HANDLE hFile,

DWORD dwFlags);

这两个函数均用于找出用户系统上的文件映像(使用上一章中介绍的搜索算法),并设法将D L L的文件映像映射到调用进程的地址空间中。两个函数返回的 H I N S TA N C E值用于标识文件映像映射到的虚拟内存地址。如果 D L L不能被映射到进程的地址空间,则返回 N U L L。若要了解关于错误的详细信息,可以调用G e t L a s t E r r o r.对于参数 d w F l a g s ,必须将它设置为 0 ,或者设置为D O N T _ R E S O LV E _ D L L _ R E F E R E N C E S、L O A D _ L I B R A RY _ A S _ D ATA F I L E和L O A D _ W I T H _A LT E R E D _ S E A R C H _ PAT H等标志的一个组合。
1. DON T_RESOLV E _ D L L _ R E F E R E N C E S
DON T_RESOLV E _ D L L _ R E F E R E N C E S标志用于告诉系统将D L L映射到调用进程的地址空间中。通常情况下,当D L L被映射到进程的地址空间中时,系统要调用 D L L中的一个特殊函数,即 D l l M a i n(本章后面介绍)。该函数用于对 D L L进行初始化。 DON T_RESOLV E _D L L _ R E F E R E N C E S标志使系统不必调用D l l M a i n函数就能映射文件映像。此外,D L L能够输入另一个D L L中包含的函数。当系统将一个 D L L映射到进程的地址空间中时,它也要查看该 D L L 是否需要其他的 D L L ,并且自动加载这些 D L L 。当 D O NT _ R E S O LV E _ D L L _ R E F E R E N C E S标志被设定时,系统并不自动将其他的 D L L加载到进程的地址空间中。
2. LOAD_LIBRARY _ A S _ D ATA F I L E
L O A D _ L I B R A RY _ A S _ D ATA F I L E标志与DON T_RESOLV E _ D L L _ R E F E R E N C E S标志相类似,因为系统只是将D L L映射到进程的地址空间中,就像它是数据文件一样。系统并不花费额外的时间来准备执行文件中的任何代码。例如,当一个 D L L被映射到进程的地址空间中时,系统要查看D L L中的某些信息,以确定应该将哪些页面保护属性赋予文件的不同的节。如果设定了L O A D _ L I B R A RY _ A S _ D ATA F I L E标志,系统将以它要执行文件中的代码时的同样方式来设置页面保护属性。
由于下面几个原因,该标志是非常有用的。首先,如果有一个 D L L(它只包含资源,但不 包含函数),那么可以设定这个标志,使 D L L的文件映像能够映射到进程的地址空间中。然后
可以在调用加载资源的函数时,使用 L o a d L i b r a r y E x函数返回的H I N S TA N C E值。通常情况下,加载一个. e x e文件,就能够启动一个新进程,但是也可以使用 L o a d L i b r a r y E x函数将. e x e文件的映像映射到进程的地址空间中。借助映射的 . e x e文件的H I N S TA N C E值,就能够访问文件中的资源。由于. e x e文件没有D l l M a i n函数,因此,当调用L o a d L i b r a r y E x来加载一个. e x e文件时,必须设定L O A D _ L I B R A RY _ A S _ D ATA F I L E标志。
3. LOAD_WITH_ALT E R E D _ S E A R C H _ PAT H
L O A D _ W I T H _ A LT E R E D _ S E A R C H _ PAT H标志用于改变 L o a d L i b r a r y E x用来查找特定的D L L文件时使用的搜索算法。通常情况下, L o a d L i b r a r y E x按照第1 9章讲述的顺序进行文件的搜索。但是,如果设定了L O A D _ W I T H _ A LT E R E D _ S E A R C H _ PAT H标志,那么L o a d L i b r a r y E x函数就按照下面的顺序来搜索文件:
1) pszDLLPathName参数中设定的目录。
2) 进程的当前目录。
3) Wi n d o w s的系统目录。
4) Wi n d o w s目录。
5) PAT H环境变量中列出的目录。

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

FARPROC GetProcAddress(

HINSTANCE hinstDll,

PCSTR pszSymbolName);

注意,参数p s z S y m b o l N a m e的原型是P C S T R,而不是P C T S T R。这意味着G e t P r o c A d d r e s s函数只接受A N S I字符串,决不能将U n i c o d e字符串传递给该函数,因为编译器/链接程序总是将符号名作为A N S I字符串存储在D L L的输出节中。这两种方法都能够提供包含在D L L中的必要符号的地址。如果D L L模块的输出节中不存在你需要的符号,G e t P r o c A d d r e s s就返回N U L L,表示运行失败。

一个D L L可以拥有单个进入点函数。系统在不同的时间调用这个进入点函数,这个问题将在下面加以介绍。这些调用可以用来提供一些信息,通常用于供 D L L进行每个进程或线程的初始化和清除操作。如果你的 D L L不需要这些通知信息,就不必在 D L L源代码中实现这个函数。例如,如果你创建一个只包含资源的 D L L,就不必实现该函数。注意 必须记住,D L L使用D l l M a i n函数来对它们进行初始化。当你的D l l M a i n函数执行时,同一个地址空间中的其他D L L可能尚未执行它们的D l l M a i n函数。这意味着它们尚未初始化,因此你应该避免调用从其他D L L中输入的函数。此外,你应该避免从D l l M a i n内部调用L o a d L i b r a r y ( E x )和F r e e L i b r a r y函数,因为这些函数会形式一个依赖性循环。Platform SDK文档说,你的D l l M a i n函数只应该进行一些简单的初始化,比如设置本地存储器(第 2 1章介绍),创建内核对象和打开文件等。你还必须避免调用 U s e r、S h e l l、O D B C、C O M、R P C和套接字函数(即调用这些函数的函数),因为它们的D L L也许尚未初始化 ,或者这些函数可能在内部调用L o a d L i b r a r y ( E x )函数,这同样会形成一个依赖性循环。

当D L L被初次映射到进程的地址空间中时,系统将调用该 D L L的D l l M a i n函数,给它传递参数f d w R e a s o n的值D L L _ P R O C E S S _ AT TA C H。只有当D L L的文件映像初次被映射时,才会出现这种情况。如果线程在后来为已经映射到进程的地址空间中的 D L L调用L o a d L i b r a r y ( E x )函数,那么操作系统只是递增 D L L的使用计数,它并不再次用 D L L _ P R O C E S S _ AT TA C H的值来调用D L L的D l l M a i n函数。D L L从进程的地址空间中被卸载时,系统将调用 D L L的D l l M a i n函数,给它传递f d w R e a s o n的值D L L _ P R O C E S S _ D E TA C H。当D L L处理这个值时,它应该执行任何与进程相关的清除操作。例如,D L L可以调用H e a p D e s t r o y函数来撤消它在D L L _ P R O C E S S _ D E TA C H通知期间创建的堆栈。注意,如果 D l l M a i n函数接收到D L L _ P R O C E S S _ D E TA C H通知时返回FA L S E,那么D l l M a i n就不是用D L L _ P R O C E S S _ D E TA C H通知调用的。如果因为进程终止运行而使 D L L被卸载,那么调用E x i t P r o c e s s函数的线程将负责执行 D l l M a i n函数的代码。在正常情况下,这是应用程序的主线程。当你的进入点函数返回到 C / C + +运行期库的启动代码时,该启动代码将显式调用E x i t P r o c e s s函数,终止进程的运行。如果因为进程中的线程调用F r e e L i b r a r y或F r e e L i b r a r y A n d E x i t T h r e a d函数而将D L L卸载,那么调用函数的线程将负责执行 D l l M a i n函数的代码。如果使用F r e e L i b r a r y,那么要等到D l l M a i n函数完成对D L L _ P R O C E S S _ D E TA C H通知的执行后,该线程才从对 F r e e L i b r a r y函数的调用中返回。

系统是顺序调用D L L的D l l M a i n函数的。为了理解这样做的意义,可以考虑下面这样一个环境。假设一个进程有两个线程,线程 A和线程B。该进程还有一个D L L,称为S o m e D L L . d l l,它被映射到了它的地址空间中。两个线程都准备调用 C r e a t e T h r e a d函数,以便再创建两个线程,即线程C和线程D。当线程A调用C r e a t e T h r e a d来创建线程C时,系统调用带有 D L L _ T H R E A D _ AT TA C H值的S o m e D L L . d l l的D l l M a i n函数。当线程C执行D l l M a i n函数中的代码时,线程B调用C r e a t e T h r e a d函数来创建线程D。这时系统必须再次调用带有D L L _ T H R E A D _ AT TA C H值的D l l M a i n函数,这次是让线程D 执行代码。但是,系统是顺序调用 D l l M a i n函数的,因此系统会暂停线程 D的运行,直到线程C完成对D l l M a i n函数中的代码的处理并且返回为止。当线程C完成D l l M a i n的处理后,它就开始执行它的线程函数。这时系统唤醒线程 D,让它
处理D l l M a i n中的代码。当它返回时,线程D开始处理它的线程函数。

有下面这样一个例子:

BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD fdwReason,Pvoid fImpLoad){

HANDLE hThread;

DWORD dwThreadId;

switch(fdwReason){

case DLL_PROCESS_ATTACH:

DisableThreadLibraryCalls(hinstDll);

hThread = CreateThread(NULL,0,SOmeFunction,NULL,0,&dwThreadId);

WaitForSingleObject(hThread,INFINITE);

CloseHandle(hThread);

break;

case DLL_THREAD_ATTACH:

break;

........

}

return TRUE;

}

当 D l l M a i n收 到D L L _ P R O C E S S _ AT TA C H 通 知 时 , 一 个 新 线 程 就 创 建 了 。 系 统 必 须 用D L L _ T H R E A D _ AT TA C H的值再次调用 D l l M a i n函数。但是,新线程被暂停运行,因为导致D L L _ P R O C E S S _ AT TA C H被发送给D l l M a i n函数的线程尚未完成处理操作。问题是调用Wa i t F o r S i n g l e O b j e c t函数而产生的。这个函数使当前正在运行的线程暂停运行,直到新线程终止运行。但是新线程从未得到机会运行,更不要说终止运行,因为它处于暂停状态,等待当前线程退出D l l M a i n函数。这里我们得到的是个死锁条件。两个线程将永远处于暂停状态。

下面是一个调用dll中的函数示例:

#include<stdio.h>
#include<Windows.h>
int main(){
int (*send)(UINT32,const char*,int);
HINSTANCE dll = LoadLibrary(TEXT("media_redirect.dll"));
if(dll == NULL) {
printf("error: dll not found.\n");
exit(0);
}
send = (int(*)(UINT32,const char*,int))GetProcAddress(dll, "SendVideoData");
if (add == NULL) {
printf("error no: %d\n", GetLastError());
exit(0);
}
char data[100]={0};int sz=12;
data[0]='c';
data[3]='c';
const char* p=data;
send(23,p,sz);
return 0;
}



So BadJust So SoGoodCoolPretty Cool (1 人已评分, 平均分: 4.00 )
Loading...

发表评论

电子邮件地址不会被公开。 必填项已用*标注