C++Builder 工程裡使用 Visual C++ DLL

來源:互聯網
上載者:User
 

在 C++Builder 工程裡調用 DLL 函數

  調用 Visual C++ DLL 給 C++Builder 程式員提出了一些獨特的挑戰。在我們試圖解決 Visual C++
產生的 DLL 之前,回顧一下如何調用一個 C++Builder 建立的 DLL 可能會有所協助。調用 C++Builder 建立的 DLL
要比 Visual C++ 的少了許多障礙。

  為了在你的 C++Builder 工程裡調用 DLL,你需要三種元素:DLL 本身,帶有函數原型的標頭檔,和引入庫(你可以在運行時載入 DLL,而不是使用引入庫,但為了簡單我們按引入庫的方法做)。調用 DLL 函數,首先通過選擇菜單 Project | Add to Project 的方法,把引入庫添加到你的 C++Builder 工程裡;其次,在需要調用 DLL 函數的 C++ 源檔案裡為 DLL 標頭檔插入 #include 聲明;最後添加調用 DLL 函數的代碼。

  程式清單 A 和 B 包含了做為測試 DLL 的原始碼。注意,測試代碼實現了兩種不同的調用習慣(__stdcall 和
__cdecl)。這樣幫是有充分的理由的。當你設法調用一個用 Visual C++ 編譯的 DLL
時,大多讓你頭疼的事情都是由於處理不同的調用習慣產生的。還要注意一點,有一個函數,它沒有明確列出使用的調用習慣。這個未知函數作為不列出調用習慣的
DLL 函數的標識。

//------------------------------------------
// 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 建立測試 DLL,開啟 C++Builder,選擇菜單 File | New 調出 Object Repository。選擇 DLL 表徵圖,單擊 OK 按鈕。C++Builder 會建立一個新的工程,帶有一個源檔案。這個檔案包含一個 DLL 的入口函數和一些 include 聲明。現在選擇 File | New Unit。儲存新的單元為 DLL.CPP。從清單 A 拷貝粘貼文本插入標頭檔 DLL.H。從清單 B 拷貝代碼,把它插入 DLL.CPP。確定 #define _BUILD_DLL_ 位於 #include "DLL.H" 聲明的上面。

  儲存工程為 BCBDLL.BPR。接下來,編譯工程,看看產生的檔案。C++Builder 產生了一個 DLL 和以 .LIB 為副檔名的引入庫。

  這時,你有了在 C++Builder 裡調用 DLL 所需的三個元素:DLL
本身,帶有函數原型的標頭檔,用來串連的引入庫。現在我們需要一個用來調用 DLL 函數的 C++Builder 工程。在 C++Builder
裡建立一個新的工程,儲存到你的硬碟上。從 DLL 工程目錄裡拷貝 DLL、引入庫、DLL.H 標頭檔到新的目錄。其次,在主單元裡添加
#include 聲明,包含 DLL.H。最後,添加調用 DLL 函數的代碼。清單 C 列出了調用由清單 A 和 B 產生的 DLL
中每個函數的代碼。

//------------------------------------------
// 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 帶來的問題

  在理想世界裡,調用 Visual C++ 建立的 DLL 不會比調用 C++Builder 建造的 DLL
難。不幸地,Borland 和 Microsoft 有幾點不一致的地方。首先,Borland 和 Microsoft 在 OBJ
和引入庫的檔案格式上不同(Visual C++ 使用 COFF 庫格式,而 Borland 使用 OMF 格式)。這就意味著你不能把一個
Microsoft 產生的引入庫添加到C++Builder 的工程裡。感謝 Borland IMPLIB 這個工具 + 生產力,檔案格式的不同得以克服。

  兩個產品在串連名字(linker name)習慣上也不同。這是 C++Builder 調用 Visual C++ DLL
的主要障礙。在 DLL 或 OBJ
裡的每一個函數有一個串連名字。連接器用串連名字在串連期間解決(resolve)聲明了原型的函數。如果連接器不能找到它認為是程式需要的串連名字的函
數,它將產生一個未解決的外部錯誤(unresolved external error)。

  關於函數串連名字,Borland 和 Microsoft 在下面兩點上不同:

  • 1- Visual C++ 有時修飾匯出的 __stdcall 函數。
  • 2- Borland C++Builder 在引入這個被修飾的函數時,認為是 __cdecl 函數。

  那麼,這件事為什麼這樣重要呢?拿分歧#1 __stdcall 調用習慣來說。如果你用 Visual C++ 建立了一個
DLL,它包含一個 __stdcall 修飾的函數叫做 MyFunction(),Visual C++ 將給函數一個串連名字,為
_MyFunction@4。當 Borland 連接器設法解決調用構造這個函數的時候,它認為要找一個名為 MyFunction 的函數。因為
Visual C++ DLL 引入庫不包含叫作 MyFunction 的函數,Borland
連接器報告一個未解決的外部錯誤,意識是沒有找到函數。

  解決這三個問題的方法要依賴 Visual C++ DLL 的編譯方式。我把整個過程分為四步。

第1步:識別在 Visual C++ DLL 裡使用的調用習慣

  為了與命名習慣纏結交戰,你必須首先確定在 DLL 裡函數使用的調用習慣。你可以通過查看 DLL 的標頭檔來確定。在 DLL 標頭檔裡的函數原型形式如下:

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

  CALLING_CONVENTION 應該是 __stdcall 或 __cdecl(具體例子參見清單 A)。很多時候,調用習慣沒有被指定,在這種情況下預設為 __cdecl。

第2步:檢查 DLL 裡的串連名字

  如果在第 1 步中顯示 DLL 利用 __stdcall 調用習慣,你需要進一步檢查 DLL,確定 Visual C++
在建立它時採用的命名習慣。Visual C++ 預設情況下要修飾 __stdcall 函數,但如果寫這個 DLL
的程式員在他們的工程裡增加一個 DEF 檔案,可以阻止命名修飾。如果供應商沒有使用 DEF 檔案,你的工會稍微繁瑣一些。

  命令列工具 TDUMP 允許你檢查 DLL 匯出函數的串連名字。下面向 DLL 調用 TDUMP 的命令。

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

  TDUMP 能報告許多關於 DLL 的資訊。我們僅對 DLL 的匯出函數感興趣。-ee 命令選項指示 TDUMP
僅列出匯出資訊。-m 開關告訴 TDUMP 按 DLL 函數的原始格式顯示。如果沒有 -m 開關,TDUMP
將嘗試把修飾過的函數轉化為人們易讀的格式。如果 DLL 很大的話,你應該重新導向 TDUMP 的輸出到一個檔案裡(通過附加的 >
MYDLL.LST)。

  TDUMP 為來源程式清單 A 和 B 的測試 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 函數上的首碼底線和尾碼 @4。__cdecl 和未指定調用方式的函數沒有任何修飾符。如果 Visuall C++ DLL 編譯的時候帶 DEF 檔案,在 __stdcall 函數上的修飾符將不會出現。

第3步:為 Visual C++ DLL 產生一個引入庫

  這是關鍵區段。由於 C++Builder 和 Visual C++ 的庫檔案格式不同,你不能把 Visual C++
建立的引入庫添加到你的 C++Builder 工程裡。你必須用隨 C++Builder 一起發行的命令列工具建立一個 OMF
格式的引入庫。依靠上面兩步得出的結論,這一步或者很順利,或者需要一些時間。

  如前面所述,C++Builder 和 Visual C++ 在關於怎樣給 DLL 函數命名上是不一致的。由於命名習慣的不同,如果
C++Builder 和 Visual C++ 對 DLL 調用習慣的實現不一致,你需要建立一個帶有別名的引入庫。表 A 列出了不一致的地方。

表A:Visual C++和C++Builder命名習慣

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

  C++Builder 欄列出 Borland 連接器想要找的串連名字。第一個 Visual C++ 欄列出 Visual C++
工程裡沒有使用 DEF 檔案時的串連名字。第二個 Visual C++ 欄包含了使用 DEF 檔案時 Visual C++
建立的串連名字。注意,兩個產品僅在一種情況下一致:Visual C++ 工程包含 DEF 檔案的 __stdcall
函數。下一關,你需要建立一個帶有別名的引入庫,使 Visual C++ 命名與 C++Builder 命名相一致。

表 A 顯示出幾種你在建立引入庫時可能需要處理的組合。我把組合分成兩種情況。

第 1 種情況:DLL 只包含 __stdcall 函數,DLL 供應商利用了 DEF 檔案

  表 A 顯示,僅當 DLL 使用了 __stdcall 函數時 VC++ 和 C++Builder 是一致的。而且,DLL 必須帶有
DEF 檔案編譯,以防止 VC++ 修飾串連名字。標頭檔會告訴你是否使用了 __stdcall 調用習慣(第 1 步),TDUMP
將顯示函數是否被修飾(第 2 步)。如果 DLL 包含沒有被修飾的 __stdcall 函數,Visual C++ 和 C++Buidler
在給函數命名上保持一致。你可以運行 IMPLIB 為 DLL 建立一個引入庫。不需要別名。

IMPLIB 的命令格式如下:

  IMPLIB (destination lib name) (source dll)

例如:

  IMPLIB mydll.lib mydll.dll

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

  如果你的 DLL 供營商堅持建立於編譯器無關的 DLL,你很幸運地可以把它歸入第 1 種情況。不幸地,有幾種可能使你不能把它歸入第
1 種情況。第一,如果 DLL 供應商在函式宣告的時候省略了調用習慣,則預設為 __cdecl,__cdecl 強迫你進入情況
2。第二,即使你的供應商利用了 __stdcall 調用習慣,他們可能忽視了利用 DEF 檔案去掉 Visual C++ 的修飾符。

  然而你找到了這裡,Good Day,歡迎來到第 2 種情況。你被用一個函數名與 C++Builder 不同的 DLL
困住。擺脫這個麻煩的唯一辦法就是建立一個引入庫,為 Visual C++ 的函數名定義一個和 C++Builder
的格式相容的別名。幸運地,C++Builder 命令列工具允許你建立一個帶有別名的引入庫。

  第一步,用 C++Builder 帶的 IMPDEF 程式給 Visual C++ DLL 建立一個 DEF 檔案。IMPDEF 建立的 DEF 檔案可以列出 DLL 匯出的所有函數。你可以這樣調用IMPDEF:

  IMPDEF (Destination DEF file) (source DLL file)

例如:

  IMPDEF mydll.def mydll.dll

  運行 IMPDEF 之後,選擇一個編輯器開啟產生的 DEF 檔案。對用 Visual C++ 編譯來源程式清單 A 和 B 產生 DLL,IMPDEF 建立的 DEF 檔案如下:

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

  下一步將修改 DEF 檔案,讓 DLL 函數的別名看起來和 C++Builder 的函數一樣。你可以這樣建立一個 DLL
函數的別名,列出一個 C++Builder 相容的名字,後面接原始的 Visual C++ 串連名字。對於程式清單 A 和 B 的測試 DLL
來說,帶別名的 DEF 如下:

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

  注意,在左邊的函數名與表 A 中 Borland 相容的名字相匹配。在右邊的函數名是真實的 Visual C++ DLL 函數的串連名字。

  最後一步將從別名 DEF 檔案建立一個別名引入庫。你又要靠 IMPLIB 公用程式了,只是這一次,用別名 DEF 檔案做為源檔案代替它原來的 DLL。格式為:

  IMPLIB (dest lib file) (source def file)

例如:

  IMPLIB mydll.lib mydll.def

  建立了引入庫,還要繼續進行到第四步。你首先應該檢查引入庫,以保證每一個 DLL 函數與 C++Builder 具有一致的命名格式。你可以用 TLIB 公用程式檢查引入庫。

  TLIB mydll.lib, mydll.lst

為測試 DLL 產生的列表檔案如下:

    Publics by module

StdCallFunction size = 0
StdCallFunction

_CdeclFunction size = 0
_CdeclFunction

_UnknownFunction size = 0
_UnknownFunction

第 4 步:把引入庫添加到你的工程裡

  一旦你為 Visual C++ DLL 建立了一個引入庫,你可以用菜單 Project | Add to Project 把它添加到你的 C++Builder 工程裡。你使用引入庫的時候不必考慮它是否包含有別名。把這個引入庫添加到你的工程裡的之後,建造(build)你的工程,看看是不是可以成功的串連。

結束語:

  這篇文章為你示範了如何在 C++Builder 工程裡調用 Visual C++ DLL 的函數。這些技巧對 C++Builder
1 和 C++Builder 3,Visual C++ 4.x 或 Visual C++ 5 建立的 DLL 生效(我還沒有測試 Visual
C++ 6)。

  你可能注意到,這篇文章僅討論了如何調用 DLL 裡 C 風格的函數。沒有嘗試去做調用 Visual C++ DLL
對象的方法。因為對於成員函數的串連名字被改編(mangled),C++ DLL 表現出更加困難的問題。編譯器要使用一種名字改編(name
mangling)方案,以支援函數重載。不幸地,C++ 標準沒有指定編譯器應當如何改編類的方法。由於沒有一個嚴格的標準到位,Borland 和
Microsoft 各自為名字改編髮展了他們自己的技術,並且兩者的習慣是不相容的。在理論上,你可以用同樣的別名技術調用位於 DLL
裡的一個類的成員函數。但你應該考慮建立一個 COM 物件來代替。COM 帶來了許多它自己的問題,但它強制執行以一種標準方式調用對象的方法。由
Visual C++ 建立的 COM 物件可以在任一開發環境裡被調用,包括 Delphi 和 C++Builder。

  C++Builder 3.0 引入了一個新的命令列公用程式叫做 COFF2OMF.EXE。這個公用程式可以把 Visual C++
引入庫轉化為 C++Builder 的引入庫。此外,對 __cdecl 函數,這個程式還會自動的產生從 Visual C++ 格式到
C++Builder 格式的別名。如果 DLL 專用 __cdecl 調用習慣,自動別名可以簡化第 3 步。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.