一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

C++Builder和Visual C++之間互相用dll的方法

 louis.sun 2010-09-13

在 C++Builder 工程里使用 Visual C++ DLL——第1部分:C函數(shù)

 

譯者序:

  第一次讀這篇文章是在 2001 年 10 月,幫我解決了一點(diǎn)小問(wèn)題。本來(lái)不好意思翻譯,因?yàn)橛⒄Z(yǔ)水平實(shí)在太差。最近發(fā)現(xiàn)不少網(wǎng)友在問(wèn)在 C++Builder 的工程里調(diào)用 Visual C++ DLL 的問(wèn)題,也許是用 C++Builder 的人比以前多了吧。于是把心一橫,不就是板兒磚嘛?“拋磚引玉”,希望它能給你幫點(diǎn)小忙,也歡迎指出翻譯中的錯(cuò)誤。

source:http://www./articles/vcdll.htm

  很可能有一天你的老板問(wèn)你是否能用 C++Builder 創(chuàng)建一個(gè) GUI,調(diào)用現(xiàn)有的用 Microsoft Visual C++ 編譯的 32 位 DLL。經(jīng)常地,原始 DLL 的源代碼不會(huì)提供給你,也許因?yàn)?DLL 來(lái)自第三方供應(yīng)商,也可能是 22 歲的實(shí)習(xí)生不小心從網(wǎng)絡(luò)上刪除了 \DLL\SRC 目錄。給你一個(gè) DLL 和頭文件,這篇文章為你示范如何在你的 C++Builder 工程里調(diào)用這種 DLL。

    * 在 C++Builder 工程里調(diào)用 DLL 函數(shù)
    * Visual C++ DLL 帶來(lái)的問(wèn)題
    * 第1步:識(shí)別在 Visual C++ DLL 里使用的調(diào)用習(xí)慣
    * 第2步:檢查 DLL 里的連接名字
    * 第3步:為 Visual C++ DLL 生成引入庫(kù)
    * 第4步:把引入庫(kù)添加到你的工程里
    * 結(jié)束語(yǔ)

在 C++Builder 工程里調(diào)用 DLL 函數(shù)

  調(diào)用 Visual C++ DLL 給 C++Builder 程序員提出了一些獨(dú)特的挑戰(zhàn)。在我們?cè)噲D解決 Visual C++ 生成的 DLL 之前,回顧一下如何調(diào)用一個(gè) C++Builder 創(chuàng)建的 DLL 可能會(huì)有所幫助。調(diào)用 C++Builder 創(chuàng)建的 DLL 要比 Visual C++ 的少了許多障礙。

  為了在你的 C++Builder 工程里調(diào)用 DLL,你需要三種元素:DLL 本身,帶有函數(shù)原型的頭文件,和引入庫(kù)(你可以在運(yùn)行時(shí)載入 DLL,而不是使用引入庫(kù),但為了簡(jiǎn)單我們按引入庫(kù)的方法做)。調(diào)用 DLL 函數(shù),首先通過(guò)選擇菜單 Project | Add to Project 的方法,把引入庫(kù)添加到你的 C++Builder 工程里;其次,在需要調(diào)用 DLL 函數(shù)的 C++ 源文件里為 DLL 頭文件插入 #include 聲明;最后添加調(diào)用 DLL 函數(shù)的代碼。

  程序清單 A 和 B 包含了做為測(cè)試 DLL 的源代碼。注意,測(cè)試代碼實(shí)現(xiàn)了兩種不同的調(diào)用習(xí)慣(__stdcall 和 __cdecl)。這樣幫是有充分的理由的。當(dāng)你設(shè)法調(diào)用一個(gè)用 Visual C++ 編譯的 DLL 時(shí),大多讓你頭疼的事情都是由于處理不同的調(diào)用習(xí)慣產(chǎn)生的。還要注意一點(diǎn),有一個(gè)函數(shù),它沒(méi)有明確列出使用的調(diào)用習(xí)慣。這個(gè)未知函數(shù)作為不列出調(diào)用習(xí)慣的 DLL 函數(shù)的標(biāo)識(shí)。

//------------------------------------------
// Listing A: DLL.H

#ifdef __cplusplus
extern "C" {
#endif

#ifdef _BUILD_DLL_
#define FUNCTION __declspec(dllexport)
#else
#define FUNCTION __declspec(dllimport)
#endif

FUNCTION int __stdcall   StdCallFunction(int Value);
FUNCTION int __cdecl     CdeclFunction  (int Value);
FUNCTION int             UnknownFunction(int Value);

#ifdef __cplusplus
}
#endif


//------------------------------------------
//Listing B: DLL.C

#define _BUILD_DLL_
#include "dll.h"

FUNCTION int __stdcall StdCallFunction(int Value)
{
    return Value + 1;
}

FUNCTION int __cdecl   CdeclFunction(int Value)
{
    return Value + 2;
}

FUNCTION int UnknownFunction(int Value)
{
    return Value;
}

  從清單 A 和 B 創(chuàng)建測(cè)試 DLL,打開 C++Builder,選擇菜單 File | New 調(diào)出 Object Repository。選擇 DLL 圖標(biāo),單擊 OK 按鈕。C++Builder 會(huì)創(chuàng)建一個(gè)新的工程,帶有一個(gè)源文件。這個(gè)文件包含一個(gè) DLL 的入口函數(shù)和一些 include 聲明。現(xiàn)在選擇 File | New Unit。保存新的單元為 DLL.CPP。從清單 A 拷貝粘貼文本插入頭文件 DLL.H。從清單 B 拷貝代碼,把它插入 DLL.CPP。確定 #define _BUILD_DLL_ 位于 #include "DLL.H" 聲明的上面。

  保存工程為 BCBDLL.BPR。接下來(lái),編譯工程,看看生成的文件。C++Builder 生成了一個(gè) DLL 和以 .LIB 為擴(kuò)展名的引入庫(kù)。

  這時(shí),你有了在 C++Builder 里調(diào)用 DLL 所需的三個(gè)元素:DLL 本身,帶有函數(shù)原型的頭文件,用來(lái)連接的引入庫(kù)?,F(xiàn)在我們需要一個(gè)用來(lái)調(diào)用 DLL 函數(shù)的 C++Builder 工程。在 C++Builder 里創(chuàng)建一個(gè)新的工程,保存到你的硬盤上。從 DLL 工程目錄里拷貝 DLL、引入庫(kù)、DLL.H 頭文件到新的目錄。其次,在主單元里添加 #include 聲明,包含 DLL.H。最后,添加調(diào)用 DLL 函數(shù)的代碼。清單 C 列出了調(diào)用由清單 A 和 B 生成的 DLL 中每個(gè)函數(shù)的代碼。

//------------------------------------------
// Listing C: MAINFORM.CPP - DLLTest program
#include <vcl\vcl.h>
#pragma hdrstop

#include "MAINFORM.h"
#include "dll.h"
//---------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}
//---------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    int Value = StrToInt(Edit1->Text);
    int Result= StdCallFunction(Value);
    ResultLabel->Caption = IntToStr(Result);
}
//---------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
    int Value = StrToInt(Edit1->Text);
    int Result= CdeclFunction(Value);
    ResultLabel->Caption = IntToStr(Result);
}
//---------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
    int Value = StrToInt(Edit1->Text);
    int Result= UnknownFunction(Value);
    ResultLabel->Caption = IntToStr(Result);
}

Visual C++ DLL 帶來(lái)的問(wèn)題

  在理想世界里,調(diào)用 Visual C++ 創(chuàng)建的 DLL 不會(huì)比調(diào)用 C++Builder 建造的 DLL 難。不幸地,Borland 和 Microsoft 有幾點(diǎn)不一致的地方。首先,Borland 和 Microsoft 在 OBJ 和引入庫(kù)的文件格式上不同(Visual C++ 使用 COFF 庫(kù)格式,而 Borland 使用 OMF 格式)。這就意味著你不能把一個(gè) Microsoft 生成的引入庫(kù)添加到C++Builder 的工程里。感謝 Borland IMPLIB 這個(gè)實(shí)用工具,文件格式的不同得以克服。

  兩個(gè)產(chǎn)品在連接名字(linker name)習(xí)慣上也不同。這是 C++Builder 調(diào)用 Visual C++ DLL 的主要障礙。在 DLL 或 OBJ 里的每一個(gè)函數(shù)有一個(gè)連接名字。連接器用連接名字在連接期間解決(resolve)聲明了原型的函數(shù)。如果連接器不能找到它認(rèn)為是程序需要的連接名字的函數(shù),它將產(chǎn)生一個(gè)未解決的外部錯(cuò)誤(unresolved external error)。

  關(guān)于函數(shù)連接名字,Borland 和 Microsoft 在下面兩點(diǎn)上不同:

    * 1- Visual C++ 有時(shí)修飾導(dǎo)出的 __stdcall 函數(shù)。
    * 2- Borland C++Builder 在引入這個(gè)被修飾的函數(shù)時(shí),認(rèn)為是 __cdecl 函數(shù)。

  那么,這件事為什么這樣重要呢?拿分歧#1 __stdcall 調(diào)用習(xí)慣來(lái)說(shuō)。如果你用 Visual C++ 創(chuàng)建了一個(gè) DLL,它包含一個(gè) __stdcall 修飾的函數(shù)叫做 MyFunction(),Visual C++ 將給函數(shù)一個(gè)連接名字,為 _MyFunction@4。當(dāng) Borland 連接器設(shè)法解決調(diào)用構(gòu)造這個(gè)函數(shù)的時(shí)候,它認(rèn)為要找一個(gè)名為 MyFunction 的函數(shù)。因?yàn)?Visual C++ DLL 引入庫(kù)不包含叫作 MyFunction 的函數(shù),Borland 連接器報(bào)告一個(gè)未解決的外部錯(cuò)誤,意識(shí)是沒(méi)有找到函數(shù)。

  解決這三個(gè)問(wèn)題的方法要依賴 Visual C++ DLL 的編譯方式。我把整個(gè)過(guò)程分為四步。
第1步:識(shí)別在 Visual C++ DLL 里使用的調(diào)用習(xí)慣

  為了與命名習(xí)慣纏結(jié)交戰(zhàn),你必須首先確定在 DLL 里函數(shù)使用的調(diào)用習(xí)慣。你可以通過(guò)查看 DLL 的頭文件來(lái)確定。在 DLL 頭文件里的函數(shù)原型形式如下:

  __declspec(dllimport) void CALLING_CONVENTION MyFunction(int nArg);

  CALLING_CONVENTION 應(yīng)該是 __stdcall 或 __cdecl(具體例子參見清單 A)。很多時(shí)候,調(diào)用習(xí)慣沒(méi)有被指定,在這種情況下默認(rèn)為 __cdecl。
第2步:檢查 DLL 里的連接名字

  如果在第 1 步中顯示 DLL 利用 __stdcall 調(diào)用習(xí)慣,你需要進(jìn)一步檢查 DLL,確定 Visual C++ 在創(chuàng)建它時(shí)采用的命名習(xí)慣。Visual C++ 默認(rèn)情況下要修飾 __stdcall 函數(shù),但如果寫這個(gè) DLL 的程序員在他們的工程里增加一個(gè) DEF 文件,可以阻止命名修飾。如果供應(yīng)商沒(méi)有使用 DEF 文件,你的工會(huì)稍微繁瑣一些。

  命令行工具 TDUMP 允許你檢查 DLL 導(dǎo)出函數(shù)的連接名字。下面向 DLL 調(diào)用 TDUMP 的命令。

  TDUMP -ee -m MYDLL.DLL > MYDLL.LST

  TDUMP 能報(bào)告許多關(guān)于 DLL 的信息。我們僅對(duì) DLL 的導(dǎo)出函數(shù)感興趣。-ee 命令選項(xiàng)指示 TDUMP 僅列出導(dǎo)出信息。-m 開關(guān)告訴 TDUMP 按 DLL 函數(shù)的原始格式顯示。如果沒(méi)有 -m 開關(guān),TDUMP 將嘗試把修飾過(guò)的函數(shù)轉(zhuǎn)化為人們易讀的格式。如果 DLL 很大的話,你應(yīng)該重定向 TDUMP 的輸出到一個(gè)文件里(通過(guò)附加的 > MYDLL.LST)。

  TDUMP 為源程序清單 A 和 B 的測(cè)試 DLL 輸出如下:

  Turbo Dump Version 5.0.16.4 Copyright (c) 1988, 1998 Borland International
  Display of File DLL.DLL

  EXPORT ord:0000='CdeclFunction'
  EXPORT ord:0002='UnknownFunction'
  EXPORT ord:0001='_StdCallFunction@4'

  注意在 __stdcall 函數(shù)上的前綴下劃線和后綴 @4。__cdecl 和未指定調(diào)用方式的函數(shù)沒(méi)有任何修飾符。如果 Visuall C++ DLL 編譯的時(shí)候帶 DEF 文件,在 __stdcall 函數(shù)上的修飾符將不會(huì)出現(xiàn)。
第3步:為 Visual C++ DLL 生成一個(gè)引入庫(kù)

  這是關(guān)鍵部分。由于 C++Builder 和 Visual C++ 的庫(kù)文件格式不同,你不能把 Visual C++ 創(chuàng)建的引入庫(kù)添加到你的 C++Builder 工程里。你必須用隨 C++Builder 一起發(fā)行的命令行工具創(chuàng)建一個(gè) OMF 格式的引入庫(kù)。依靠上面兩步得出的結(jié)論,這一步或者很順利,或者需要一些時(shí)間。

  如前面所述,C++Builder 和 Visual C++ 在關(guān)于怎樣給 DLL 函數(shù)命名上是不一致的。由于命名習(xí)慣的不同,如果 C++Builder 和 Visual C++ 對(duì) DLL 調(diào)用習(xí)慣的實(shí)現(xiàn)不一致,你需要?jiǎng)?chuàng)建一個(gè)帶有別名的引入庫(kù)。表 A 列出了不一致的地方。

表A:Visual C++和C++Builder命名習(xí)慣

調(diào)用習(xí)慣    VC++ 命名       VC++ (使用了DEF)    C++Builder 命名
-----------------------------------------------------------------
__stdcall   _MyFunction@4   MyFunction          MyFunction
__cdecl     MyFunction      MyFunction          _MyFunction

  C++Builder 欄列出 Borland 連接器想要找的連接名字。第一個(gè) Visual C++ 欄列出 Visual C++ 工程里沒(méi)有使用 DEF 文件時(shí)的連接名字。第二個(gè) Visual C++ 欄包含了使用 DEF 文件時(shí) Visual C++ 創(chuàng)建的連接名字。注意,兩個(gè)產(chǎn)品僅在一種情況下一致:Visual C++ 工程包含 DEF 文件的 __stdcall 函數(shù)。下一關(guān),你需要?jiǎng)?chuàng)建一個(gè)帶有別名的引入庫(kù),使 Visual C++ 命名與 C++Builder 命名相一致。

表 A 顯示出幾種你在創(chuàng)建引入庫(kù)時(shí)可能需要處理的組合。我把組合分成兩種情況。

第 1 種情況:DLL 只包含 __stdcall 函數(shù),DLL 供應(yīng)商利用了 DEF 文件

  表 A 顯示,僅當(dāng) DLL 使用了 __stdcall 函數(shù)時(shí) VC++ 和 C++Builder 是一致的。而且,DLL 必須帶有 DEF 文件編譯,以防止 VC++ 修飾連接名字。頭文件會(huì)告訴你是否使用了 __stdcall 調(diào)用習(xí)慣(第 1 步),TDUMP 將顯示函數(shù)是否被修飾(第 2 步)。如果 DLL 包含沒(méi)有被修飾的 __stdcall 函數(shù),Visual C++ 和 C++Buidler 在給函數(shù)命名上保持一致。你可以運(yùn)行 IMPLIB 為 DLL 創(chuàng)建一個(gè)引入庫(kù)。不需要?jiǎng)e名。

IMPLIB 的命令格式如下:

  IMPLIB (destination lib name) (source dll)

例如:

  IMPLIB mydll.lib mydll.dll

第 2 種情況:DLL 包含 __cdecl 函數(shù)或者被修飾的 __stdcall 函數(shù)

  如果你的 DLL 供營(yíng)商堅(jiān)持創(chuàng)建于編譯器無(wú)關(guān)的 DLL,你很幸運(yùn)地可以把它歸入第 1 種情況。不幸地,有幾種可能使你不能把它歸入第 1 種情況。第一,如果 DLL 供應(yīng)商在函數(shù)聲明的時(shí)候省略了調(diào)用習(xí)慣,則默認(rèn)為 __cdecl,__cdecl 強(qiáng)迫你進(jìn)入情況 2。第二,即使你的供應(yīng)商利用了 __stdcall 調(diào)用習(xí)慣,他們可能忽視了利用 DEF 文件去掉 Visual C++ 的修飾符。

  然而你找到了這里,Good Day,歡迎來(lái)到第 2 種情況。你被用一個(gè)函數(shù)名與 C++Builder 不同的 DLL 困住。擺脫這個(gè)麻煩的唯一辦法就是創(chuàng)建一個(gè)引入庫(kù),為 Visual C++ 的函數(shù)名定義一個(gè)和 C++Builder 的格式兼容的別名。幸運(yùn)地,C++Builder 命令行工具允許你創(chuàng)建一個(gè)帶有別名的引入庫(kù)。

  第一步,用 C++Builder 帶的 IMPDEF 程序給 Visual C++ DLL 創(chuàng)建一個(gè) DEF 文件。IMPDEF 創(chuàng)建的 DEF 文件可以列出 DLL 導(dǎo)出的所有函數(shù)。你可以這樣調(diào)用IMPDEF:

  IMPDEF (Destination DEF file) (source DLL file)

例如:

  IMPDEF mydll.def mydll.dll

  運(yùn)行 IMPDEF 之后,選擇一個(gè)編輯器打開產(chǎn)生的 DEF 文件。對(duì)用 Visual C++ 編譯源程序清單 A 和 B 生成 DLL,IMPDEF 創(chuàng)建的 DEF 文件如下:

  EXPORTS
      ; use this type of aliasing
      ; (Borland name)   = (Name exported by Visual C++)
      _CdeclFunction   = CdeclFunction
      _UnknownFunction = UnknownFunction
      StdCallFunction  = _StdCallFunction@4

  下一步將修改 DEF 文件,讓 DLL 函數(shù)的別名看起來(lái)和 C++Builder 的函數(shù)一樣。你可以這樣創(chuàng)建一個(gè) DLL 函數(shù)的別名,列出一個(gè) C++Builder 兼容的名字,后面接原始的 Visual C++ 連接名字。對(duì)于程序清單 A 和 B 的測(cè)試 DLL 來(lái)說(shuō),帶別名的 DEF 如下:

  EXPORTS
      ; use this type of aliasing
      ; (Borland name) = (Name exported by Visual C++)
      _CdeclFunction = CdeclFunction
      _UnknownFunction = UnknownFunction
      StdCallFunction = _StdCallFunction@4

  注意,在左邊的函數(shù)名與表 A 中 Borland 兼容的名字相匹配。在右邊的函數(shù)名是真實(shí)的 Visual C++ DLL 函數(shù)的連接名字。

  最后一步將從別名 DEF 文件創(chuàng)建一個(gè)別名引入庫(kù)。你又要靠 IMPLIB 實(shí)用程序了,只是這一次,用別名 DEF 文件做為源文件代替它原來(lái)的 DLL。格式為:

  IMPLIB (dest lib file) (source def file)

例如:

  IMPLIB mydll.lib mydll.def

  創(chuàng)建了引入庫(kù),還要繼續(xù)進(jìn)行到第四步。你首先應(yīng)該檢查引入庫(kù),以保證每一個(gè) DLL 函數(shù)與 C++Builder 具有一致的命名格式。你可以用 TLIB 實(shí)用程序檢查引入庫(kù)。

  TLIB mydll.lib, mydll.lst

為測(cè)試 DLL 生成的列表文件如下:

    Publics by module

    StdCallFunction size = 0
            StdCallFunction

    _CdeclFunction  size = 0
            _CdeclFunction

    _UnknownFunction size = 0
            _UnknownFunction

第 4 步:把引入庫(kù)添加到你的工程里

  一旦你為 Visual C++ DLL 創(chuàng)建了一個(gè)引入庫(kù),你可以用菜單 Project | Add to Project 把它添加到你的 C++Builder 工程里。你使用引入庫(kù)的時(shí)候不必考慮它是否包含有別名。把這個(gè)引入庫(kù)添加到你的工程里的之后,建造(build)你的工程,看看是不是可以成功的連接。
結(jié)束語(yǔ):

  這篇文章為你示范了如何在 C++Builder 工程里調(diào)用 Visual C++ DLL 的函數(shù)。這些技巧對(duì) C++Builder 1 和 C++Builder 3,Visual C++ 4.x 或 Visual C++ 5 創(chuàng)建的 DLL 生效(我還沒(méi)有測(cè)試 Visual C++ 6)。

  你可能注意到,這篇文章僅討論了如何調(diào)用 DLL 里 C 風(fēng)格的函數(shù)。沒(méi)有嘗試去做調(diào)用 Visual C++ DLL 對(duì)象的方法。因?yàn)閷?duì)于成員函數(shù)的連接名字被改編(mangled),C++ DLL 表現(xiàn)出更加困難的問(wèn)題。編譯器要使用一種名字改編(name mangling)方案,以支持函數(shù)重載。不幸地,C++ 標(biāo)準(zhǔn)沒(méi)有指定編譯器應(yīng)當(dāng)如何改編類的方法。由于沒(méi)有一個(gè)嚴(yán)格的標(biāo)準(zhǔn)到位,Borland 和 Microsoft 各自為名字改編發(fā)展了他們自己的技術(shù),并且兩者的習(xí)慣是不兼容的。在理論上,你可以用同樣的別名技術(shù)調(diào)用位于 DLL 里的一個(gè)類的成員函數(shù)。但你應(yīng)該考慮創(chuàng)建一個(gè) COM 對(duì)象來(lái)代替。COM 帶來(lái)了許多它自己的問(wèn)題,但它強(qiáng)制執(zhí)行以一種標(biāo)準(zhǔn)方式調(diào)用對(duì)象的方法。由 Visual C++ 創(chuàng)建的 COM 對(duì)象可以在任一開發(fā)環(huán)境里被調(diào)用,包括 Delphi 和 C++Builder。

  C++Builder 3.0 引入了一個(gè)新的命令行實(shí)用程序叫做 COFF2OMF.EXE。這個(gè)實(shí)用程序可以把 Visual C++ 引入庫(kù)轉(zhuǎn)化為 C++Builder 的引入庫(kù)。此外,對(duì) __cdecl 函數(shù),這個(gè)程序還會(huì)自動(dòng)的產(chǎn)生從 Visual C++ 格式到 C++Builder 格式的別名。如果 DLL 專用 __cdecl 調(diào)用習(xí)慣,自動(dòng)別名可以簡(jiǎn)化第 3 步。

 

在 C++Builder 工程里使用 Visual C++ DLL——第2部分:C++ 類

 

 

source:http://www./articles/vcdll2.htm

注意:這篇文章描述如何把 C++ 類從 Visual C++ DLL 引入到 BCB 的工程中。在我們開始之前,我覺得必須給出一點(diǎn)警告。這篇文章不是真的準(zhǔn)備大量發(fā)布的。如果“文章”跌宕起伏,難以閱讀,或包含錯(cuò)誤,我道賺!我沒(méi)有時(shí)間去改良它。我決定繼續(xù)并發(fā)布的唯一原因是因?yàn)楹芏嚅_發(fā)者問(wèn)到怎么處理這個(gè)問(wèn)題。我認(rèn)為,一篇寫的很爛的文章總比什么都沒(méi)有好。我希望這個(gè)不連貫概念的搜集品會(huì)給你帶來(lái)幫助。

在上一篇文章如何“在 C++Builder 工程里使用 Visual C++ DLL”中,我描述了如何為 MSVC DLL 創(chuàng)建一個(gè) Borland 兼容的引入庫(kù)。主要的難點(diǎn)在于 MSVC 和 Borland 使用的函數(shù)命名格式不同。舉例來(lái)說(shuō),Borland 認(rèn)為 __cdecl 函數(shù)在它們的開頭有一個(gè)下劃線,但 MSVC 沒(méi)有。幸運(yùn)的是,你可以用 Borland 命令行實(shí)用工具克服名稱的不同,這些工具有 TDUMP、IMPLIB、IMPDEF、和 COFF2OMF。方法是用這些命令行工具創(chuàng)建一個(gè)帶有 Borland 兼容函數(shù)名的 Borland 兼容引入庫(kù)。一旦你擁有了 Borland 兼容引入庫(kù),你便可以開始工作了。你可以簡(jiǎn)單的連接引入庫(kù)來(lái)使用 MSVC DLL。

不幸地,這種策略不能完全帶你走出這片森林。在上一篇 DLL 文章的結(jié)尾,我丟下了一個(gè)小炸彈。你只能調(diào)用 MSVC DLL 里簡(jiǎn)單的 C 函數(shù),而不能引入類或類成員函數(shù)。Doh!

那么如果你需要從 MSVC DLL 引入 C++ 類要做些什么呢?啊……這個(gè),如果是那樣的話,你就被關(guān)到角落里了,沒(méi)有多少可選擇的余地(通常在你退到角落里的時(shí)候,你的選項(xiàng)都不是令人滿意的)。這篇文描述了三種可以帶你走出角落的方法。

壞消息:當(dāng)你準(zhǔn)備花點(diǎn)時(shí)間研究這篇垃圾的時(shí)候,我覺得,再次,被迫發(fā)出警告。所有三種技術(shù)需要你有 Microsoft Visual C++。你不需要有要調(diào)用的 DLL 的源代碼,但你需要有可以調(diào)它的工具。三種技術(shù)都或多或少使用包裝技術(shù),我們用 MSVC 把 DLL 包裝成可以在 BCB 里使用的某種形式。

    * 三種技術(shù)摘要
    * 技術(shù) 1: 把 C++ 類包裹到 C 庫(kù)里
    * 技術(shù) 2: 創(chuàng)建 COM 包裝
    * 技術(shù) 3: 使用帶虛函數(shù)的抽象基類(pseudo-COM)
    * 結(jié)論
    * 下載

三種技術(shù)摘要

Ok, 現(xiàn)丑了。這就是那三種技術(shù)。

   1. 用 MSVC 創(chuàng)建一個(gè) DLL,把 C++ 類包裹成簡(jiǎn)單的 C 函數(shù)。簡(jiǎn)單的 C 函數(shù)是可以在 BCB 里引入的。
   2. 用 MSVC 創(chuàng)建一個(gè) COM 對(duì)象,把 C++ 類經(jīng)過(guò)限制包裝。BCB 可以作為 COM 客戶端來(lái)調(diào)用 VC++ COM 對(duì)象。
   3. 把 C++ 類用抽象類包裝起來(lái),這個(gè)抽象類只帶有一些沒(méi)有實(shí)體的虛函數(shù)。這從本質(zhì)上說(shuō)還是 COM,只是沒(méi)有了難看的部分。

下面描述各種技術(shù)的更多詳細(xì)內(nèi)容。在每一個(gè)例子中,我們將假定 MSVC DLL 導(dǎo)出的類形式如下:

class CFoo
{
public:
    CFoo(int x);
    ~CFoo();

    int DoSomething(int y);
};

技術(shù) 1: 把 C++ 類包裹到 C 庫(kù)里

在前一篇有關(guān) VC++ DLL 的文章里,我們知道在一個(gè) Borland 工程里調(diào)用從一個(gè) MSVC DLL 導(dǎo)出的簡(jiǎn)單的 C 函數(shù)是可能的。利用這條信息可知,我們可以在 MSVC 里創(chuàng)建一個(gè) DLL 工程,來(lái)導(dǎo)出簡(jiǎn)單的 C 函數(shù)給 BCB 用。這個(gè) MSVC 包裹的 DLL 會(huì)作為 C++ DLL 的客戶端。包裹 DLL 將導(dǎo)出簡(jiǎn)單的 C 函數(shù),以創(chuàng)建的 CFoo 對(duì)象調(diào),調(diào)用 CFoo 成員函數(shù),和銷毀 CFoo 對(duì)象。

CFoo 類包含三個(gè)我們關(guān)心的函數(shù):構(gòu)造函數(shù),析構(gòu)函數(shù),和所有重要的 DoSomething 函數(shù)。我們需要把每一個(gè)函數(shù)包裹成與其等價(jià)的 C 函數(shù)。

// original class
class CFoo
{
public:
    CFoo(int x);
    ~CFoo();

    int DoSomething(int y);
};

// flattened C code
void* __stdcall new_CFoo(int x)
{
    return new CFoo(x);
}

int __stdcall CFoo_DoSomething(void* handle, int y)
{
    CFoo *foo = reinterpret_cast<CFoo *>(handle);
    return foo->DoSomething(y);
}

void __stdcall delete_CFoo(void *handle)
{
    CFoo *foo = reinterpret_cast<CFoo *>(handle);
    delete foo;
}

這里有幾個(gè)比較重要的地方要注意。首先,注意每一個(gè) C++ 成員函數(shù)被映射為一個(gè)簡(jiǎn)單的 C 函數(shù)。其次,觀察到我們?yōu)?C 函數(shù)明確地使用 __stdcall 調(diào)用習(xí)慣。在前一篇 DLL 文章里,我們知道簡(jiǎn)單的調(diào)用在 MSVC DLL 里的無(wú)格式 C 函數(shù),真是很麻煩。如果我們放棄越過(guò)種種艱難困苦去用它,我們可以使這個(gè)努力稍微容易一點(diǎn)。讓 Borland 調(diào)用 Microsoft DLL 最簡(jiǎn)單的辦法是 DLL 導(dǎo)出無(wú)格式,無(wú)修飾,__stdcall 調(diào)用習(xí)慣的 C 函數(shù)。Borland 和 Microsoft 對(duì) __cdecl 函數(shù)的處理上是不同的。通常,他們對(duì) __stdcall 函數(shù)也不同,因?yàn)?MSVC 修飾 __stdcall 函數(shù),但我們可以通過(guò)添加一個(gè) DEF 文件到 MSVC 工程里來(lái)阻止這種行為。參見下載部分的例子有 DEF 文件的例子。

其它關(guān)于代碼要注意的事情是 new_CFoo 函數(shù)返回一個(gè)指向 CFoo 對(duì)象的指針。BCB 調(diào)用者必須在本地保存這個(gè)指針。這可能看起來(lái)和這篇文章的主題有點(diǎn)矛盾。畢竟,我想 BCB 不能使用來(lái)自 MSVC DLL 的 C++ 對(duì)象?如果那是正確的,那么為什么我們還要返回一個(gè) CFoo 對(duì)象指針呢?

答案是 BCB 不能調(diào)用 MSVC DLL 導(dǎo)出類的成員函數(shù)。但是,這并不意味著它不能存儲(chǔ)這樣對(duì)象的地址。new_CFoo 返回的是一個(gè) CFoo 對(duì)象的指針。BCB 客戶端可以存儲(chǔ)這個(gè)指針,但不能用。BCB 不能廢棄它(不應(yīng)當(dāng)嘗試這么做)。讓這個(gè)觀點(diǎn)更容易理解一點(diǎn),new_CFoo 返回一個(gè)空指針(總之它不能返回別的什么東西)。在 BCB 這邊,除了存儲(chǔ)它,然后把它傳回給 DLL,沒(méi)有什么可以安全地處理這個(gè)空指針的方法。

Ok,在我們繼續(xù)前進(jìn)之前,還有另外兩個(gè)要十分注意的地方。首先,注意 CFoo_DoSomething 把空指針作為它的第一個(gè)參數(shù)。這個(gè)空指針與 new_CFoo 返回的是同一個(gè)空指針??罩羔樣?reinterpret_cast 被追溯到 CFoo 對(duì)象(你知道,當(dāng)你看到一個(gè) reinterpret_cast 的時(shí)候,你正在處理是難看的代碼)。DoSomething 成員函數(shù)在轉(zhuǎn)換之后被調(diào)用。最后,注意空指針也是 delete_CFoo 函數(shù)的參數(shù)。包裝 DLL 刪除對(duì)象是至關(guān)緊要的。你不應(yīng)當(dāng)在 BCB 里對(duì)空指針調(diào)用 delete。顯然它不會(huì)按你想的去做。

下面的程序清單展示了 C 函數(shù)的 DLL 頭文件。這個(gè)頭文件可以在 MSVC 和 BCB 之間共享。

// DLL header file
#ifndef DLL_H
#define DLL_H

#ifdef BUILD_DLL
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#else

#ifdef __cplusplus
extern "C" {
#endif

DLLAPI void* __stdcall new_CFoo(int x);
DLLAPI int   __stdcall CFoo_DoSomething(void* handle, int y);
DLLAPI void  __stdcall delete_CFoo(void *handle);

#ifdef __cplusplus
}
#endif

#endif

這是一個(gè)典型的 DLL 頭文件。注意到一個(gè)令人好奇的事情,在頭文件里看不到 CFoo 類。頭文件僅包含用以包裝 CFoo 的無(wú)格式 C 函數(shù)。

下面的程序清單展示了如何在 BCB 里調(diào)用 DLL。

#include "dll.h"

void bar()
{
    int x = 10;
    int y = 20;
    int z;

    void * foo = new_CFoo(x);
    z = CFoo_DoSomething(foo, y);
    delete_CFoo(foo);
}

這樣就可以了。盡管不太漂亮,但還能用。事實(shí)上,不管這個(gè)技術(shù)多么奇異,在其它一些不能調(diào)用 DLL 的情形,同樣可以用這種方法。舉例來(lái)說(shuō),Delphi 程序員使用相同的技術(shù),因?yàn)?Delphi 不能調(diào)用 C++ 成員函數(shù)。Delphi 程序員必須把 C++ 類包裹成 C 代碼,并連接成 C OBJ 文件。開源工具 SWIG (swig.org) 被設(shè)計(jì)用來(lái)生成象這樣的包裝函數(shù),在那里允許你使用類似 Python 的角本語(yǔ)言調(diào)用 C++ 對(duì)象。
技術(shù) 2: 創(chuàng)建 COM 包裝

不幸地,我還沒(méi)有這種技術(shù)的例子(嗨,我說(shuō)過(guò)這篇文章不是為黃金時(shí)段準(zhǔn)備的)。但這個(gè)主意是這樣工作的。在 MSVC 里創(chuàng)建一個(gè) COM 對(duì)象?;蛟S你可以運(yùn)行向?qū)?。?chuàng)建一個(gè)進(jìn)程內(nèi)服務(wù)器(如 DLL,不是 EXE)。同樣,確認(rèn)你創(chuàng)建了一個(gè) COM 對(duì)象,而不是自動(dòng)控制對(duì)象。自動(dòng)控制只會(huì)是使每一件事更困難。除非你也需要在 VB 或 ASP 頁(yè)面用 C++ 類,那也可以用無(wú)格式 COM,而不用自動(dòng)控制。

在 COM 工程內(nèi)部,創(chuàng)建一個(gè)新的 COM 對(duì)象。MSVC 大概想讓你創(chuàng)建一個(gè) COM 接口。既然我們正在包裝一個(gè)稱做 CFoo 的類,一個(gè)好的接口名應(yīng)當(dāng)是 IFoo。MSVC 也會(huì)讓你為執(zhí)行類的 COM 對(duì)象命名。CFooImpl 是一個(gè)不錯(cuò)的候選者。

COM 對(duì)象應(yīng)當(dāng)用聚合包裝 C++ DLL 類。換句話說(shuō),COM 對(duì)象包含 CFoo 成員變量。不要設(shè)法從 CFoo 繼承你的 COM 類。對(duì)每一個(gè) C++ DLL 類(CFoo)的成員函數(shù),在你的 COM 對(duì)象里創(chuàng)建一個(gè)類似的函數(shù)。如果可能的話,用相同的名字,傳遞相同的參數(shù),返回相同類型的值。你需要調(diào)整一些事情。比如,字符串在 COM 里通常被傳遞為 BSTR。同樣,返回值被特別地傳遞為輸出參數(shù),因?yàn)?COM 方法應(yīng)當(dāng)返回一個(gè)錯(cuò)誤代碼。當(dāng)你做完這些,C++ 類的每一個(gè)成員函數(shù)在 COM 包裝里應(yīng)當(dāng)有一個(gè)相應(yīng)的函數(shù)。

在你 build COM 包裝之后,用 regsrv32.exe 注冊(cè)它。一旦注冊(cè),你應(yīng)當(dāng)能例示這個(gè) COM 對(duì)象,并且用 BCB 代碼調(diào)用它包裝的成員函數(shù)。

再一次,我為上面介紹的這種技術(shù)沒(méi)有可運(yùn)行的演示道歉。
技術(shù) 3: 使用帶虛函數(shù)的抽象基類(pseudo-COM)

技術(shù) 3 是一種 pseudo-COM 方法。COM 是一個(gè)二進(jìn)制對(duì)象規(guī)范。COM 對(duì)象可以被 BCB 和 MSVC 調(diào)用,而不管 COM 對(duì)象是用什么編譯器編譯的。因此,這個(gè)二進(jìn)制用什么魔法工作的呢?答案就是基于要講的這種技術(shù)。

COM 函數(shù)調(diào)用通過(guò)函數(shù)查找表來(lái)分派。神奇地是這個(gè)函數(shù)查找表與 C++ 虛函數(shù)表用同樣的方法正確地工作。事實(shí)上,他們就是相同的。COM 不過(guò)是虛函數(shù)和虛函數(shù)表的一種美稱的形式。

COM 可以工作,是因?yàn)?BCB 和 MSVC 真正使用相同的虛分派系統(tǒng)。COM 依賴于大多數(shù) Win32 C++ 編譯器都用相同的方法生成和使用 vtable 的這個(gè)事實(shí)。因?yàn)閮蓚€(gè)編譯器用相同的虛分派系統(tǒng),我們就能在 MSVC 里用虛函數(shù)創(chuàng)建一個(gè)包裝類,它可以被 BCB 調(diào)用。這正是 COM 所做的。

這是 pseudo-COM 包裝類的 DLL 頭文件。它包括一個(gè)抽象基類,IFoo,它服務(wù)于 pseudo-COM 接口。它還包括兩個(gè) C 函數(shù),用來(lái)創(chuàng)建和刪除 IFoo 對(duì)象。這個(gè)頭文件在 MSVC 和 BCB 之間共享。

#ifndef DLL_H
#define DLL_H

#ifdef BUILD_DLL
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

// psuedo COM interface
class IFoo
{
public:
    virtual int __stdcall DoSomething(int x) = 0;
    virtual __stdcall ~IFoo() = 0;
};

#ifdef __cplusplus
extern "C" {
#endif

DLLAPI IFoo*  __stdcall new_IFoo(int x);
DLLAPI void   __stdcall delete_IFoo(IFoo *f);

#ifdef __cplusplus
}
#endif

#endif

注意到兩個(gè) C 函數(shù)類似技術(shù) 1 的函數(shù),除了現(xiàn)在它們與 IFoo 合作,而不是空指針。這種技術(shù)比第一種提供了更多的類型安全。

這里是 MSVC 包裝的源代碼。它包括一個(gè)從 IFoo 繼承而來(lái)的稱作 CFooImpl 的類。CFooImpl 是 IFoo 接口的實(shí)現(xiàn)。

#define BUILD_DLL

#include "dll.h"

IFoo::~IFoo()
{
 // must implement base class destructor
 // even if its abstract
}

// Note: we declare the class here because no one outside needs to be concerned
//       with it.
class CFooImpl : public IFoo
{
private:
    CFoo  m_Foo; // the real C++ class from the existing MSVC C++ DLL
public:
    CFooImpl(int x);
    virtual ~CFooImpl();
    virtual int __stdcall DoSomething(int x);
};

CFooImpl::CFooImpl(int x)
    : m_Foo(x)
{
}

int __stdcall CFooImpl::DoSomething(int x)
{
    return m_Foo.DoSomething(x);
}

CFooImpl::~CFooImpl()
{
}

IFoo * __stdcall new_IFoo(int x)
{
    return new CFooImpl(x);
}

void __stdcall delete_IFoo(IFoo *f)
{
    delete f;
}

這兒有許多好的素材資料。首先,注意到現(xiàn)在我們有一個(gè)類在 BCB 和 MSVC 之間共享的頭文件。好象是一件好事。更重要的是,注意到 BCB 工程將只與 IFoo 類打交道。真正的 IFoo 實(shí)現(xiàn)由叫做 CFooImpl 的派生類提供,那是在 MSVC 工程內(nèi)部。

BCB 客戶端代碼將與 IFoo 對(duì)象以多態(tài)性合作。要得到一個(gè)包裝實(shí)例,BCB 代碼可以調(diào)用 new_IFoo 函數(shù)。new_IFoo 的工作像一個(gè)函數(shù)工廠,提供新的 IFoo 實(shí)例。new_Foo 返回一個(gè)指向 IFoo 實(shí)例的指針。然而,指針是多態(tài)的。指針的靜態(tài)類型是 IFoo,但它實(shí)際的動(dòng)態(tài)類型將被指向 CFooImpl(BCB 代碼是不知道真相的)。

這是 BCB 客戶端的代碼。

#include "dll.h"

void bar()
{
    int x = 10;
    int y = 20;
    int z;


    IFoo *foo = new_IFoo(x);
    z = foo->DoSomething(y);
    delete_IFoo(foo);
}

現(xiàn)在給出在技術(shù) 3 上某些部分的注釋。第一,至關(guān)緊要的是你從 MSVC DLL 里刪除 IFoo 指針。這個(gè)由調(diào)用 delete_IFoo 函數(shù)傳遞 IFoo 指針完成。不要嘗試從 BCB 里刪除對(duì)象。

void bar()
{
    IFoo *foo = new_IFoo(x);
    delete foo;               // BOOM!!!
}

這段代碼將在痛苦中死去。問(wèn)題是 IFoo 是被在 MSVC 包裝 DLL 里的 new_IFoo 函數(shù)創(chuàng)建的。同樣地,IFoo 對(duì)象占的內(nèi)存是被 MSVC 內(nèi)存管理器分配的。當(dāng)你刪除一個(gè)對(duì)象時(shí),只有權(quán)刪除和它用同一個(gè)內(nèi)存管理器創(chuàng)建的對(duì)象。如果你在 BCB 這邊對(duì)指針調(diào)用 delete,那么你是用 Borland 內(nèi)存管理器刪除。現(xiàn)在,我可能錯(cuò)了,但是我愿意拿我的房子和一個(gè)生殖器打賭,要么二個(gè),不能企圖讓 Microsoft 內(nèi)存管理器和 Borland 內(nèi)存管理器一起工作。當(dāng)你用 Borland 內(nèi)存管理器刪除指針的時(shí)候,難道它會(huì)嘗試聯(lián)系 Microsoft 內(nèi)存管理器,讓它知道它應(yīng)當(dāng)釋放的哪些內(nèi)存?

另外解釋一項(xiàng),BCB 代碼完全按照 IFoo 虛函數(shù)接口工作。在 BCB 這邊你看不到任何 CFooImpl 類的事件。CFooImpl 在 MSVC 包裝工程的內(nèi)存。當(dāng)你從 BCB 這邊調(diào)用 DoSomething 的時(shí)候,調(diào)用通過(guò)虛函數(shù)表被分派到 CFooImpl。

如果你在這個(gè)概念上理解有困難的話,不要著急。我或許沒(méi)有把它描述的很好。下面的內(nèi)容可以幫助理解,在 BCB 這邊,你可以用 CPU viewer 單步跟蹤代碼。它允許你單步跟蹤每一條匯編指令,看看 vtable 是怎么進(jìn)行查找工作的。
Tip  注意:

如果你使用這種 pseudo-COM 技術(shù),確定你沒(méi)有嘗試重載虛函數(shù)。換句話說(shuō),不要?jiǎng)?chuàng)建象這樣的接口:

class IFoo
{
public:
    virtual int __stdcall DoSomething(int x) = 0;
    virtual int __stdcall DoSomething(float x) = 0;
    virtual int __stdcall DoSomething(const char *x) = 0;
};

不應(yīng)當(dāng)重載虛接口函數(shù)的原因是 MSVC 和 BCB 在 vtable 上不可能(或許不會(huì))制定相同的方法。當(dāng)我試驗(yàn)重載時(shí),在 BCB 這邊調(diào)用 DoSomething(int),在 MSVC 那邊象是分派到 DoSomething(float)。Borland 和 Microsoft 在 vtable 格式上不重載的時(shí)候是一致的。這可能解釋了為什么 COM 對(duì)象不使用重載函數(shù)。

If you need to wrap a C++ class with overloaded functions, then you should create a distinct function name for each one.

class IFoo
{
public:
    virtual int __stdcall DoSomething_int  (int x) = 0;
    virtual int __stdcall DoSomething_float(float x) = 0;
    virtual int __stdcall DoSomething_str  (const char *x) = 0;
};


結(jié)論:

Ok, 我們到哪兒了?啊,在文章開始,我們講了關(guān)于為什么 BCB 不能調(diào)用 DLL 里的 C++ 成員函數(shù),如果 DLL 是被 MSVC 編譯的。原因就是兩種編譯器在成員函數(shù)命名上不一致。我們討論了三種(有點(diǎn)討厭)工作方法。每一種工作方法由一個(gè)為 C++ DLL 而建立的 MSVC 包裝 DLL。包裝 DLL 用一些 BCB 可以理解的格式揭露 C++ 類。第一種技術(shù)把每一個(gè) C++ 類的成員函數(shù)包裹成無(wú)格式的 C 函數(shù)。第二種技術(shù)把每一個(gè)成員函數(shù)映射成 COM 對(duì)象的成員。最后一種技術(shù)依賴虛函數(shù)是按查找表分派而不是名稱的事實(shí)。在這種策略里,每一個(gè) C++ 成員函數(shù)被映射成一個(gè)抽象類的虛函數(shù)。

下載部分包括這篇文章的例子代碼。第一個(gè)下載包含原始的 MSVC C++ DLL,我們?cè)O(shè)法與它合作。三種技術(shù)的每一個(gè)例程使用相同的 DLL。仍就沒(méi)有為技術(shù) 2 準(zhǔn)備例子。
下載

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    91福利视频日本免费看看| 国产欧美一区二区另类精品| 国产成人精品99在线观看| 丰满人妻少妇精品一区二区三区| 国产亚洲精品久久久优势| 黄色污污在线免费观看| 草草视频福利在线观看| 国产精品一区欧美二区| 中文字幕亚洲精品乱码加勒比| 午夜福利激情性生活免费视频| 91久久精品国产一区蜜臀| 国产精品久久香蕉国产线| 国产av精品一区二区| 太香蕉久久国产精品视频| 日韩性生活片免费观看| 亚洲成人精品免费在线观看 | 大尺度激情福利视频在线观看| 亚洲精品一区三区三区| 亚洲欧美日韩在线看片| 激情三级在线观看视频| 日本午夜福利视频免费观看| 午夜福利92在线观看| 国产一二三区不卡视频| 91蜜臀精品一区二区三区| 国产精品免费福利在线| 国产精品一区二区丝袜| 中国美女草逼一级黄片视频| 国产av一区二区三区麻豆| 草草夜色精品国产噜噜竹菊| 成年女人午夜在线视频| 国产av乱了乱了一区二区三区| 亚洲第一区二区三区女厕偷拍| 色综合伊人天天综合网中文| 成年女人下边潮喷毛片免费| 91精品国产综合久久不卡| 在线日本不卡一区二区| 国产一区二区三区香蕉av| 国产亚洲欧美另类久久久| 国产日韩在线一二三区| 91麻豆精品欧美一区| 日韩欧美在线看一卡一卡|