⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chap51.htm

📁 C++教程
💻 HTM
字号:
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<title>动态链结函式库彻底研究</title>
<link rel="stylesheet" href="../../../include/style.css">
</head>

<body>
<font SIZE="2">

<p><small><a href="../../../index.htm">首页</a> &gt;&gt; <a href="../../program.htm">程序设计</a> 
  &gt;&gt; <a href="../cbuilder.htm">C++ Builder</a>&nbsp; &gt;&gt;&nbsp; </small>动态链结函式库彻底研究</p>

<p align="left"><!--webbot bot="ImageMap" rectangle=" (40,1) (71, 23)  chap52.htm" rectangle=" (4,1) (36, 23)  chap05.htm" src="../ch1/NextBack.gif" width="72" height="24" alt="NextBack.gif (743字节)" border="0" startspan --><MAP NAME="FrontPageMap"><AREA SHAPE="RECT" COORDS="40, 1, 71, 23" HREF="chap52.htm"><AREA SHAPE="RECT" COORDS="4, 1, 36, 23" HREF="chap05.htm"></MAP><a href="../../../_vti_bin/shtml.exe/program/C++/ch5/chap51.htm/map"><img ismap usemap="#FrontPageMap" border="0" height="24" alt="NextBack.gif (743字节)" src="../ch1/NextBack.gif" width="72"></a><!--webbot bot="ImageMap" endspan i-checksum="23383" --></p>
</font>

<p align="left"><font SIZE="2" color="#FF0000">动态链结函式库彻底研究</font></p>

<p><font SIZE="2">在前面的范例中,我们已经示范了一个基础dll的撰写方式,然而那只能说是少部份的Know-How而已,接下来我想针对DLL做一个彻底的探讨,企图使您对它有一个全面的认知,同时也希望在Know-How之外,可以告诉你一些关於DLL的Know-Why。 
</font></p>

<p><font SIZE="2" color="#FF0000">DLL的生与死</font></p>

<p><font SIZE="2">DLL顾名思义,是一个可以动态链结的函式库。这其中包含两个意义。第一,它是动态链结的,也就是说它必须具有『招之即来,挥之即去』的基本特性,它只有在被需要的时候才会被载入系统中,而在不被需要时,即自系统中释放。第二,它是一个函式库,因此它的行为模式和一般的函式库没什麽不同,当它载入时,它就视同其他一般的函式般。 
</font></p>

<p><font SIZE="2" color="#FF0000">『招之即来,挥之即去』的DLL</font></p>

<p><font SIZE="2">前面我们提到,DLL必须具备『招之即来,挥之即去』的基本特性。那麽要如何载入及释放DLL呢?关於此点,我们必须分为两方面来探讨;即所谓的明确呼叫及不明确呼叫。 
</font></p>

<p><font SIZE="2">明确呼叫(explicited linked):所谓明确呼叫(explicited 
linked)是使用LoadLibrary函式来载入 DLL。使用FreeLibrary函式来释放 DLL。这种方式是由使用者主动透过LoadLibrary 
载入该 DLL,然後以GetProcAddress来取得函式位址,再呼叫该函式。最後在不使用该DLL之後,再将其释放。使用明确呼叫的优点在於,你可以完全控制该DLL的载入及释放,最有效地利用系统资源:缺点则是,必须自行利用GetProcAddress来取得叫使用的函式位址,但也由於使用了GetProcAddress来取得函式位址,因此在使用上增加许多弹性。由於此种使用方式载入函式程式是主动且可见的,因此名之为明确呼叫。 
</font></p>

<p><font SIZE="2">不明确呼叫 ( implicited linked):所谓不明确呼叫则是利用链结DLL函式库所相对应的输出函式库 
( export library),来达成呼叫函式的目的。因此载入DLL以及释放DLL的程序是不可见的,当使用该输出函式库的程式载入後,系统即将该DLL载入,当使用该输出函式的程式结束後,系统即将该DLL释放。使用不明确呼叫的优点在於,使用者可以完全不必顾虑到函式的载入及释放相关问题,使用时就如同一般的静态函式般。由於此种使用方式载入函式是非主动且不可见的,因此名之为不明确呼叫。 
</font></p>

<p><font SIZE="2" color="#FF0000">DLL的使用次数 (Usage Count)</font></p>

<p><font SIZE="2">前面提到的载入及释放其实在定义上是不明确的。为什麽呢?因为DLL的载入及释放尚牵涉到多行程使用时的载入及释放。由於DLL是动态链结的,因此可以同时有许多程式在使用同一个 
DLL,举例来说:若一个X.DLL同时被A、B、C叁个程式使用着,则X.DLL会被载入叁次。然而系统为了结省资源,当然不会重复载入,因此此时在系统内会有一个表格来记载X.DLL的使用次数。所以当A程式载入X.DLL後,B、C程式再次载入X.DLL时,此时X.DLL并没有被重复地载入,系统只是将X.DLL的使用次数加一,然後将先前载入的X.DLL位址传回给 
B、C两个程式使用,如此就可以达到共享函式库的目的了。同样地在释放X.DLL时,若该DLL同时有多人使用时,系统纯粹只是将该DLL的使用次数减一,当其使用次数等於0时,系统才会『真正』地将它由系统中释放。否则若是系统不分青红皂白即将DLL释放,会造成系统的灾难。 
</font></p>

<p><font SIZE="2">由以上可知,无论我们使用明确呼叫或是不明确呼叫,DLL的载入及释放都和它的使用次数有关。所以DLL的生与死其实和它的使用次数有关,当它的使用次数不为0时,就表示其『阳寿未尽』,系统就会维持其活动状态;反之,若其使用次数为0时,则表示它该『寿终正寝』了,系统就将其释放,并回收其使用的资源。然而若使用该DLL的程式当掉,导致该DLL没被释放时,该DLL就会因为使用次数没有被适时减少,而一直在系统内『阴魂不散』了。这种利用使用次数来管理共享资源的方法,也同时使用在OLE之中。</font> 
</p>

<p><font SIZE="2" color="#FF0000">新知识的实践</font></p>

<p><font SIZE="2">现在我们已了解DLL的使用,尚有另一种明确呼叫的方式,我们可以将前面的范例程式修改为使用明确呼叫的方法来使用 
DLL。</font></p>

<p><font SIZE="2">void (*ShowImage)(void);</font> </p>

<p><font SIZE="2">void __fastcall TForm1::ShowButtonClick(TObject *Sender)</font> </p>

<p><font SIZE="2">{</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; HINSTANCE hInst;</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; hInst = LoadLibrary(&quot;DLLSAMP.DLL&quot;);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; (FARPROC 
&amp;)ShowImage=GetProcAddress(hInst,&quot;ShowImage&quot;);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; ShowImage();</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; FreeLibrary(hInst);</font> </p>

<p><font SIZE="2">}</font></p>

<p><font SIZE="2">以上就是修改後的程式,因为程式已改成明确呼叫的方式,因此不需要使用DLLSAMP.LIB了,所以关於BCB和VC所使用的LIB档格式不同的问题也不存在了。在此我简单地说明所使用的几个函式 
</font></p>

<p><font SIZE="2">hInst = LoadLibrary(&quot;DLLSAMP.DLL&quot;) 是用来载入DLLSAMP.DLL 
,同时传回该DLL的HINSTANCE值,它是据以使用DLL的权杖。</font></p>

<p><font SIZE="2">(FARPROC &amp;)ShowImage=GetProcAddress(hInst,&quot;ShowImage&quot;) 
利用前面得到的HINSTANCE值,呼叫GetProcAddress来得到ShowImage函式的位址,因为GetProcAddress所传回的值为FARPROC 
,因此我们必须做型别转换。在此我是利用 (FARPROC &amp;) 以reference做型别转换。 
</font></p>

<p><font SIZE="2">FreeLibrary(hInst) 使用完後,利用FreeLibrary 将该DLL释放。</font> 
</p>

<p><font SIZE="2" color="#FF0000">输入函式及输出函式的标准写法</font></p>

<p><font SIZE="2">前面我们使用输入函式及输出函式时,为了简化程式的写法,因此使用了Borland为了和16位元程式相容而使用的 
_export编译指令,在此我必须指出,这种写法是非标准的写法,其实Microsoft在32位元程式中使用了另一种定义输入函式及输出函式的写法,那才是一个放诸四海皆准的写法,使用 
_export式的旧有写法在诸如Visual C++ 的编译器中是无法通过编译的。 </font></p>

<p><font SIZE="2">在理论上,我们希望可以使用单一的关键字来定义一个输出函式,就如同 
_export一般,然而Microsoft却在它的32位元程式中使用了另一种关键字来定义输入及输出函式,那就是 
__declspec关键字,它可以传入dllimport及dllexport两个参数,用来分别代表输入函式及输出函式。 
</font></p>

<p><font SIZE="2">换句话说,若你要撰写输出函式,你必须使用 
__declspec(dllexport) 
来定义该函式,反之若你要使用输入函式,则你必须使用 
__declspec(dllimport) 来定义该函式。</font></p>

<p><font SIZE="2">因此由於输入及输出函式的使用方式不同,你必须使用两个不同的include档来分别定义之。若你不想如此麻烦,那麽就必须要使用巨集定义来达到一体多用的目的罗,这对少数人持反对论点的人来说,简直是罪恶(还有人称之为巨集巫毒--macro 
woodoo)。</font></p>

<p><font SIZE="2">Windows的实作名家Jeffrey Richter,也就是Advanced Windows的作者建议我们使用以下的方法来达到一体多用的效果(同样是透过巨集巫毒)。 
</font></p>

<p><font SIZE="2">#ifndef _SHOWIMG_H_</font> </p>

<p><font SIZE="2">#define _SHOWIMG_H_</font></p>

<p><font SIZE="2">#ifndef IMGDLL</font> </p>

<p><font SIZE="2">#define EXTERN __declspec(dllimport)</font> </p>

<p><font SIZE="2">#else</font> </p>

<p><font SIZE="2">#define EXTERN __declspec(dllexport)</font> </p>

<p><font SIZE="2">#endif</font> </p>

<p><font SIZE="2">void EXTERN ShowImage(void);</font> </p>

<p><font SIZE="2">#endif</font></p>

<p><font SIZE="2">如此一来,当你在撰写DLL时撰写可以撰写如下函式:</font></p>

<p><font SIZE="2">#define IMGDLL</font> </p>

<p><font SIZE="2">#include &quot;image.h&quot;</font></p>

<p><font SIZE="2">当使用者在使用DLL时,则只要直接含入image.h即可。如此一来算是解决了利用 
__declspec(dllimport) 和 __declspec(dllexport) 的不便了。</font>

<ul>
  <li><font SIZE="2">必也正名乎的DLL函式命名</font> </li>
</ul>

<ul>
  <li><font SIZE="2">谈完了标准写法,再来我们要谈谈一个更容易搞混的函式命名原则。本来在正常情况下,我们是不需要理会编译器的函式命名规则的,因为在使用同一样编译器的情况下,不会有什麽太大的问题。然而问题来了,由於DLL是动态连结函式库,因此它的目标就是希望可以让多个程式共享程式及资源。所以若是DLL只能为同一种编译器所使用,那麽它的用途就大打折扣了。因此我们还是必须了解函式的命名方法。同时由於函式命名方式在各种不同的编译器各不相同,因此我们也必须了解其相异处,最重要的是,我们必须找出其沟通的方式。</font> 
  </li>
</ul>

<p><font SIZE="2" color="#FF0000">C++ Builder的命名规则</font>

<ul>
  <li><font SIZE="2">除了前面提到的 __declspec编译指令之外,在C++ Builder尚有几种修饰字会影响到函数的命名, 
    它们就是 __cdecl,__stdcall,__pascal,__fastcall四个修饰字。为了了解该修饰字对於函式命名的影响,我们可以用以下的程式来测试之:</font> 
  </li>
</ul>

<p><font SIZE="2">#ifndef _DLLNAME01_H_</font> </p>

<p><small>{</small></p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; #define _DLLNAME01_H_</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; #ifndef DLLNAME</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define EXTERN 
__declspec(dllimport)</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; #else</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define EXTERN 
__declspec(dllexport)</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; #endif</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; EXTERN void DllName01(void);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; EXTERN void _stdcall DllName02(void);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; EXTERN void _cdecl DllName03(void);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; EXTERN void _pascal DllName04(void);</font> </p>

<p><font SIZE="2">&nbsp;&nbsp;&nbsp; EXTERN void _fastcall DllName05(void);</font> </p>

<p><font SIZE="2">};</font> </p>

<p><font SIZE="2">#endif</font></p>

<p><font SIZE="2">以上为程式的定义,同时我们可以在 .CPP档中撰写相对应的空函式,然後将其编译成DLL档,再利用TDUMP.EXE或是VC++ 
内的DUMPBIN.EXE来观察其内容,由於TDUMP会将函式命名解码,反而会使混淆原来的名称,因此以下的输出是由DUMPBIN.EXE得来。 
</font></p>

<p align="right"><font SIZE="2"><br>
<!--webbot bot="ImageMap" rectangle=" (40,1) (71, 23)  chap52.htm" rectangle=" (4,1) (36, 23)  chap05.htm" src="../ch1/NextBack.gif" width="72" height="24" alt="NextBack.gif (743字节)" border="0" startspan --><MAP NAME="FrontPageMap1"><AREA SHAPE="RECT" COORDS="40, 1, 71, 23" HREF="chap52.htm"><AREA SHAPE="RECT" COORDS="4, 1, 36, 23" HREF="chap05.htm"></MAP><a href="../../../_vti_bin/shtml.exe/program/C++/ch5/chap51.htm/map1"><img ismap usemap="#FrontPageMap1" border="0" height="24" alt="NextBack.gif (743字节)" src="../ch1/NextBack.gif" width="72"></a><!--webbot bot="ImageMap" endspan i-checksum="41457" --></font></p>

<p><font SIZE="2"><small><a href="../../../index.htm">首页</a> &gt;&gt; <a href="../../program.htm">程序设计</a> 
  &gt;&gt; <a href="../cbuilder.htm">C++ Builder</a>&nbsp; &gt;&gt;&nbsp; </small>动态链结函式库彻底研究</font></p>
</body>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -