📄 00000004.htm
字号:
<BR>hInst = LoadLibrary("DLLSAMP.DLL") 是用来载入DLLSAMP.DLL <BR>,同时传回该DLL的HINSTANCE值,它是据以使用DLL的权杖。 <BR> <BR>(FARPROC &)ShowImage=GetProcAddress(hInst,"ShowImage") <BR>利用前面得到的HINSTANCE值,呼叫GetProcAddress来得到ShowImage函 <BR>式的位址,因为GetProcAddress所传回的值为FARPROC <BR>,因此我们必须做型别转换。在此我是利用 (FARPROC &) 以reference做 <BR>型别转换。 <BR> <BR>FreeLibrary(hInst) 使用完後,利用FreeLibrary 将该DLL释放。 <BR>输入函式及输出函式的标准写法 <BR> <BR>前面我们使用输入函式及输出函式时,为了简化程式的写法,因此使用了 <BR>Borland为了和16位元程式相容而使用的 <BR>_export编译指令,在此我必须指出,这种写法是非标准的写法,其实 <BR>Microsoft在32位元程式中使用了另一种定义输入函式及输出函式的写法, <BR>那才是一个放诸四海皆准的写法,使用 <BR>_export式的旧有写法在诸如Visual C++ 的编译器中是无法通过编译的。 <BR> <BR>在理论上,我们希望可以使用单一的关键字来定义一个输出函式,就如同 <BR>_export一般,然而Microsoft却在它的32位元程式中使用了另一种关键字 <BR>来定义输入及输出函式,那就是 <BR>__declspec关键字,它可以传入dllimport及dllexport两个叁数,用来分 <BR>别代表输入函式及输出函式。 <BR> <BR>换句话说,若你要撰写输出函式,你必须使用 __declspec(dllexport) 来 <BR>定义该函式,反之若你要使用输入函式,则你必须使用 <BR>__declspec(dllimport) 来定义该函式。 <BR> <BR>因此由於输入及输出函式的使用方式不同,你必须使用两个不同的include <BR>档来分别定义之。若你不想如此麻烦,那麽就必须要使用巨集定义来达到一 <BR>体多用的目的罗,这对少数人持反对论点的人来说,简直是罪恶(还有人称 <BR>之为巨集巫毒--macro <BR>woodoo)。 <BR> <BR>Windows的实作名家Jeffrey Richter,也就是Advanced <BR>Windows的作者建议我们使用以下的方法来达到一体多用的效果(同样是透 <BR>过巨集巫毒)。 <BR> <BR>#ifndef _SHOWIMG_H_ <BR>#define _SHOWIMG_H_ <BR> <BR>#ifndef IMGDLL <BR>#define EXTERN __declspec(dllimport) <BR>#else <BR>#define EXTERN __declspec(dllexport) <BR>#endif <BR>void EXTERN ShowImage(void); <BR>#endif <BR> <BR>如此一来,当你在撰写DLL时撰写可以撰写如下函式: <BR> <BR>#define IMGDLL <BR>#include "image.h" <BR> <BR>当使用者在使用DLL时,则只要直接含入image.h即可。如此一来算是解决 <BR>了利用 __declspec(dllimport) 和 <BR>__declspec(dllexport) 的不便了。 <BR> <BR> 必也正名乎的DLL函式命名 <BR> 谈完了标准写法,再来我们要谈谈一个更容易搞混的函式命名原则。本 <BR>来在正常情况下,我们是不需要理会编译器的函式命名规则的,因为在使用 <BR>同一样编译器的情况下,不会有什麽太大的问题。然而问题来了,由於DLL <BR>是动态连结函式库,因此它的目标就是希望可以让多个程式共享程式及资 <BR>源。所以若是DLL只能为同一种编译器所使用,那麽它的用途就大打折扣了。 <BR>因此我们还是必须了解函式的命名方法。同时由於函式命名方式在各种不同 <BR>的编译器各不相同,因此我们也必须了解其相异处,最重要的是,我们必须 <BR>找出其沟通的方式。 <BR> <BR>C++ Builder的命名规则 <BR> <BR> 除了前面提到的 __declspec编译指令之外,在C++ Builder尚有几种 <BR>修饰字会影响到函数的命名, 它们就是 <BR> __cdecl,__stdcall,__pascal,__fastcall四个修饰字。为了了解该 <BR>修饰字对於函式命名的影响,我们可以用以下的程式来测试之: <BR> <BR>#ifndef _DLLNAME01_H_ <BR>#define _DLLNAME01_H_ <BR>#ifndef DLLNAME <BR>#define EXTERN __declspec(dllimport) <BR>#else <BR>#define EXTERN __declspec(dllexport) <BR>#endif <BR>EXTERN void DllName01(void); <BR>EXTERN void _stdcall DllName02(void); <BR>EXTERN void _cdecl DllName03(void); <BR>EXTERN void _pascal DllName04(void); <BR>EXTERN void _fastcall DllName05(void); <BR>}; <BR>#endif <BR> <BR>以上为程式的定义,同时我们可以在 .CPP档中撰写相对应的空函式,然後 <BR>将其编译成DLL档,再利用TDUMP.EXE或是VC++ <BR>内的DUMPBIN.EXE来观察其内容,由於TDUMP会将函式命名解码,反而会使 <BR>混淆原来的名称,因此以下的输出是由DUMPBIN.EXE得来。 <BR> <BR> <BR> <BR> <BR> <BR>函式定义 DLL内的函式名 摘要说明 <BR>void DllName01(void) @DllName01$qv 因为是CPP程式码 <BR>void _stdcall DllName02(void) @DllName02$qqsv 所以函式名都被修 <BR>void _cdecl DllName03(void) @DllName03$qv 饰过。 <BR>void _pascal DllName04(void) @DLLNAME04$QV <BR>void _fastcall DllName05(void) @DllName05$qqrv <BR> <BR> 以上结果是否令你丈二金钢、摸不着头绪。这是因为我们的程式名称若 <BR>以CPP为延伸名,C++Builder会以C++ <BR> 特有的命名方式来为函式命名,这种命名方式会在函式名称後加上其使 <BR>用叁数的性质,如叁数类别等。这在C++ 中有一个特别的名称,叫做mangled <BR> name,这是一种为了要实作出多载函式所发出的命名规则。(注:在C++ 中 <BR>Add(int) 和Add(double) <BR> 可以同时存在,因此必须在object <BR> code区分之)。同时这种命名方式由於各个编译器厂商使用的方式各不 <BR>相同,因此在撰写DLL时要避免使用之。为了要避开以上问题,我们改以下 <BR>列的宣告方式: <BR>#define _DLLNAME01_H_ <BR>#ifndef DLLNAME <BR>#define EXTERN __declspec(dllimport) <BR>#else <BR>#define EXTERN __declspec(dllexport) <BR>#endif <BR>extern "C" { <BR>EXTERN void DllName011(void); <BR>EXTERN void _stdcall DllName022(void); <BR>EXTERN void _cdecl DllName033(void); <BR>EXTERN void _pascal DllName044(void); <BR>EXTERN void _fastcall DllName055(void); <BR>}; <BR>#endif <BR> <BR>其中extern "C" {厖厖.}; 是用来告诉编译器使用C的命名方式,不要使用 <BR>C++ 的mangled <BR>name。若是其中只有一个函式时,你可以直接以下列方式宣告之: <BR>extern "C" void __stdcall ShowImage(); <BR> <BR>现在我们可以检视除去mangled name後的函式名称: <BR> <BR>函式定义 DLL内的函式名 摘要说明 <BR>void DllName01(void) _DllName01 名称加底线 <BR>void _stdcall DllName02(void) DllName02 名称未变 <BR>void _cdecl DllName03(void) _DllName03 名称加底线 <BR>void _pascal DllName04(void) DLLNAME04 名称大写 <BR>void _fastcall DllName05(void) @DllName05 名称加@ <BR> <BR>以上我们可得知,在未加修饰字时和使用_cdecl修饰字时的名称是一样的。 <BR>而 _pascal修饰字所产生的函式名则和16位元的标准DLL 函式名相同(这 <BR>在VC++ <BR>是不被接受的),__fastcall的函式名称则加上 @。 <BR> <BR>其中在WIN32中使用最多的是 _stdcall修饰字,这也是你要撰写一个可以 <BR>和其他语言共同使用时所使用的修饰字,其次则为 <BR>__cdecl修饰字,这是用来传送不定叁数型别的函式如printf、sprintf等 <BR>使用的。其馀两者几乎在DLL没有机会使用。 <BR> <BR>结论:由上可知,在C++Builder中撰写DLL时必须注意以下事项: <BR> 使用 __declspec(dllimport)及 __declspec(dllexport)的标准型 <BR>式。 <BR> 注意C++ 的函式名称编码(mangled name)。 <BR> 注意修饰字的使用。除非使用不定叁数的函式,否则必使用 __stdcall <BR>修 <BR>饰字。 <BR>(4) 不要把 __declspec的使用和 __stdcall混淆了。此二者并没有绝对 <BR>的相关性。即使是程式老手都可能栽在此处,切记,切记! <BR> <BR>怎麽样,在看完了以上的介绍後,是否有晃然大悟的感觉。在了解以上的规 <BR>则後,今後不论在撰写或是使用DLL时遭遇连结的问题时,应该难不倒你吧! <BR>最後,我们将标准的DLL宣告方式列於後,以加深你的印象: <BR> <BR>#ifndef _SHOWIMG_H_ <BR>#define _SHOWIMG_H_ <BR>#ifndef IMGDLL <BR>#define EXTERN __declspec(dllimport) <BR>#else <BR>#define EXTERN __declspec(dllexport) <BR>#endif <BR>extern "C" EXTERN void __stdcall ShowImage(void); <BR>#endif <BR> <BR>语言双雄' C++Builder 和Visual C++ 连结 <BR> <BR>前面我们已经把关於C++Builder撰写DLL所应注意到的事项介绍完了,现 <BR>在我们来谈另一个重点 - C++Builder和Visual C++ <BR>的连结。若是你没有使用过Visual C++ 的话,可以将此部份略去。若是你 <BR>在程式设计时必须使用到Visual C++ 的DLL或是必须提供DLL给VC++ <BR>或是VB使用时,也许会带给你意想不到的收获。 <BR> <BR>VC++ 使用C++Builder的DLL函式 <BR> <BR>在Visual C++ 中使用C++Builder的DLL的函式方法和在C++Builder中使 <BR>用大同小异,唯有几件事情必须要注意。 <BR> <BR>(一)Visual C++ 的LIB档格式和C++Builder的LIB格式不同,因此你必 <BR>须重新产生一个 LIB。不过,可惜的是VC++ <BR>在32位元的版本中并未提供IMPLIB.EXE函式(这点一直令许多人百思不 <BR>解),因此你无法很方便地产生LIB档。解决方法有二:其一是在VC++ <BR>内撰写一个同名称的空的DLL函式,令其产生LIB档,其二则是使用 <BR>LoadLibrary、GetProcAddress式的明确呼叫方式。 <BR>(二)使用前面提到的标准写法。 <BR> <BR>C++Builder中使用VC++ 的DLL函式 <BR> <BR>在C++Builder中使用VC++ 的DLL函式时要注意的是Microsoft在Visual C++ <BR>中使用的特殊命名规则。在VC++ <BR>中命名规则除了前面谈到的几项之外,它还使用了一个特殊的叁数命名法, <BR>简言之,就是在函数名称後面加上叁数的大小,这种命名方法会造成 <BR>C++Builder,VB,Delphi使用的上的困扰。举例来说 <BR> <BR> <BR>extern "C" _declspec(dllexport) void __stdcall ShowImage(void); <BR> <BR>在VC++ 中产生的函式名称为ShowImage@0(其中0表示叁数大小),而不是 <BR>如在C++Builder中产生的ShowImage,这是VC++ <BR>已知的问题,这个问题也造成了很多使用non-VC++ 的使用者的问题,解决 <BR>之道是在该DLL的DEF档中加上以下的叙述 <BR>EXPORTS <BR>ShowImage=ShowImage@0 <BR>如此便可以产生正确的函式名了,若是你不想修改DEF档,你也可以在程式 <BR>中加入以下的连结指引 <BR>#pragma comment(linker,"/exports:ShowImage=ShowImage@0") <BR>假设你不确定其正确的名称,可以利用DumpBin或是TDump观察之。 <BR> <BR>以上是针对VC++ 的程式设计的所作的额外说明。最後我们以一个VC++ 程 <BR>式呼叫本单元的About Dialog DLL做为结束。 <BR> <BR> <BR> <BR>此程式的关键程式码如下: <BR>void CVcusedllApp::OnAppAbout() <BR>{ <BR>void (*ShowImage)(void); <BR>HINSTANCE hInst; <BR>hInst = LoadLibrary("DLLSAMP2.DLL"); <BR>(FARPROC &)ShowImage=GetProcAddress(hInst,"ShowImage"); <BR>ShowImage(); <BR>FreeLibrary(hInst); <BR>} <BR> <BR> <BR>-- <BR> <BR> 面对未知的世界因恐惧而发抖 <BR> <BR> <BR> <BR> <BR> <BR>※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 166.111.49.104] <BR><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -