将 COFF C 对象文件与 Delphi X2 一起使用






4.71/5 (7投票s)
仍在為尋找終極的 COFF 到 OMF 轉換器來鏈接 Delphi 代碼而苦苦掙扎?您不再需要了。
引言
用 Delphi 開發軟體並希望鏈接額外對象文件的開發者,可以因為 Embarcadero 的兩個原因而感到高興:第一個是因為 Embarcadero 並沒有為新的 64 位編譯器提供任何 OMF(對象文件格式)擴展,它只使用 COFF。第二個原因是 Delphi XE2 在編譯 32 位代碼時(除了 OMF 外),也可以鏈接 COFF 對象文件。
背景
有數百萬行 'C' 代碼等待編譯並鏈接 Delphi 以獲得漂亮的 GUI。我知道 Embarcadero 有 C++ Builder,但對於純粹(無 GUI)的 C 或 C++ 開發,它在各方面都落後於 Visual Studio。
我們如何在 Delphi 中使用 Visual Studio 編譯的 "C" 代碼?到目前為止,這幾乎是不可能的,因為 Visual Studio 編譯為 COFF,而 Delphi 只懂 OMF,從 COFF 到 OMF 的轉換器並不可靠,因為 Delphi 使用的 OMF 有許多專有且未記錄的部分。
我之前提到過,最新的 Delphi 版本可以讀取 COFF 對象文件,但我沒有說實際將這些對象文件與 Delphi 鏈接是一件容易的事。
那麼,將 Visual Studio 編譯的代碼鏈接 Delphi XE2 是容易還是不容易?
答案是:“不難,只需注意一些細節。對於 64 位代碼甚至更容易。”
使用代码
為了展示如何操作,讓我們打開 VS 2010,在“文件”菜單中選擇“新建項目”,然後轉到“Visual C++”部分。您可以在“常規”節點中選擇“空項目”,但最好選擇 Win32 項目節點下的“Win32 控制台應用程序”或“Win32 項目”,因為這樣您就可以在 VS 中測試整個項目。完成此操作後,從“項目”菜單添加一個 C++ 文件和一個標頭文件。這兩個文件將是我們將放置與 Delphi 編譯和鏈接的代碼的地方。在“解決方案資源管理器”中,右鍵單擊您剛添加的新 C++ 文件,選擇“屬性”,然後在“C/C++”節點的“高級”子節點中,選擇“編譯為 C 代碼 (/TC)”,然後在“預編譯標頭”子節點中,選擇“不使用預編譯標頭”。現在,在“項目屬性”的“常規”節點中,選擇“使用 Unicode 字節集”。禁用項目中所有與異常處理相關的設置,以避免複雜化(即,“啟用 C++ 異常”=“否”和“緩衝區安全檢查”=“否”)。最後,在“優化”子節點中將“整個程序優化”設置為“否”。
您已準備就緒,可以開始編寫 C 函數了。完成後,右鍵單擊源文件並選擇“編譯”。編譯 32 位和 64 位版本,然後將*.obj* 文件複製到您擁有 Delphi XE2 項目的文件夾中。
Delphi 鏈接器既不知道 VS2010 中定義的函數參數的信息,也不知道在鏈接時需要由 Delphi 鏈接器解析的所有外部引用。因此,您需要在 Delphi 單元中聲明所有這些。
要鏈接 32 位 Delphi 程序,黃金法則如下:
- 所有函數聲明都將以底線開頭,並且調用約定應為 cdecl(好吧,這是默認的)。
- 所有外部引用都應具有 cdecl 調用規範;如果調用 Windows API,您必須在 VS 中使用具有 cdecl 調用規範的中間函數(請參閱我們的演示程序,了解我們如何解決對
MessageBoxW
API 的調用)。 - 帶有 cdecl 調用規範的 Genuine 外部引用可能是*msvcrt.dll* 的導出,您可以從 Delphi 程序中使用此 DLL,而無需找出替換函數(請參閱我們的演示程序了解如何操作)。
鏈接 VS 對象文件與 64 位 Delphi XE2 程序更容易,因為:
- 只有一種調用約定,即 fastcall。
- 函數名稱沒有底線。
- 對 Windows API 的外部引用無需我們操作即可直接解析。
- 使用*msvcrt.dll* 函數僅需在 Delphi 中聲明它們即可。
我提供了一個包含完整源代碼的演示,如果您在這方面沒有經驗,強烈建議您下載並學習。
下面顯示了 Delphi 源文件。請注意我之前提到的所有要點。對於 32 位:帶底線的函數、cdecl 調用約定、直接使用*msvcrt.dll* 導出的函數,以及在調用 Windows API(stdcall)時在 Visual Studio 中使用中間函數,並帶有 cdecl 擴展。
如您在下面看到的,對於 64 位,它更簡單,主要是因為只有一種調用約定。
unit VsAndDelphi;
interface
{$IF CompilerVersion < 23}
-> Requiires Delphi XE2 or later
{$IFEND}
uses
Winapi.Windows, Winapi.Messages, System.SysUtils,
System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm2 = class(TForm)
GroupBox1: TGroupBox;
lblFirstValue: TLabel;
edFirstValue: TEdit;
lblSecondValue: TLabel;
edSecondValue: TEdit;
btAddValues: TButton;
GroupBox2: TGroupBox;
lblCaption: TLabel;
lblMessage: TLabel;
edCaption: TEdit;
edMessage: TEdit;
btShowMessage: TButton;
lblDispStrings: TLabel;
Label5: TLabel;
GroupBox3: TGroupBox;
lblPubIntVar: TLabel;
edPublicIntVal: TEdit;
btGeetCVars: TButton;
btGetString: TButton;
lblPublicStrVar: TLabel;
edPublicStrVal: TEdit;
procedure btAddValuesClick(Sender: TObject);
procedure btShowMessageClick(Sender: TObject);
procedure btGeetCVarsClick(Sender: TObject);
procedure btGetStringClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
type
bigarray = array[0..127] of char;
{$IFDEF CPUX86}
{$L CtoDelphi32.obj}
function _addNumbers(value1 : integer; value2: integer):integer;cdecl;external;
function _wcscpy_s(S1:PChar; count: size_t; S2: PChar): Integer; cdecl;
external 'msvcrt.dll' name 'wcscpy_s';
function _wcscat_s(S1:PChar; count: size_t; S2: PChar): Integer;
external 'msvcrt.dll' name 'wcscat_s';
procedure _cShowGetMessage(incaption: string; intext:string;
size : integer; var retVal: bigArray);cdecl;external;
function _MessageBoxW2(theHwnd:HWND; lpText : PWideCHAR;
lpCaption : PWideCHAR; uType:UINT):integer; cdecl;
// Actually, these are not procedures but pointers to the VS variables:
procedure _publicCInteger;external;
procedure _publicCArray;external;
// Public variable to be accessed from C
var
_myDelphiPublicIntVariable : integer;
_myDelphiPublicStrVariable : string;
{$ELSE}
{$IFDEF CPUX64}
{$L CtoDelphi64.obj}
function addNumbers(value1 : integer; value2: integer):integer;external;
procedure cShowGetMessage(incaption:string; intext:string;
size : integer; var retVal: bigArray);external;
function wcscpy_s(S1:PChar; count: size_t; S2: PChar): Integer;
external 'msvcrt.dll' name 'wcscpy_s';
function wcscat_s(S1:PChar; count: size_t; S2: PChar): Integer;
external 'msvcrt.dll' name 'wcscat_s';
// Actually, these are not procedures but pointers to the VS variables:
procedure publicCInteger;external;
procedure publicCArray;external;
// Public variable to be accessed from C
var
myDelphiPublicIntVariable : integer;
myDelphiPublicStrVariable : string;
{$ENDIF}
{$ENDIF}
implementation
{$R *.dfm}
procedure TForm2.btGeetCVarsClick(Sender: TObject);
var
myCInt : integer;
begin
{$IFDEF CPUX86}
myCInt := integer((@_publicCInteger)^);
showMessage(inttostr(myCInt));
{$ELSE}
{$IFDEF CPUX64}
myCInt := integer((@publicCInteger)^);
showMessage(inttostr(myCInt));
{$ENDIF}
{$ENDIF}
end;
procedure TForm2.btGetStringClick(Sender: TObject);
var
myCArray : pchar;
begin
{$IFDEF CPUX86}
myCArray := pchar((@_publicCArray)^);
showMessage(myCArray);
{$ELSE}
{$IFDEF CPUX64}
myCArray := pchar((@publicCArray)^);
showMessage(myCArray);
{$ENDIF}
{$ENDIF}
end;
procedure TForm2.btAddValuesClick(Sender: TObject);
var
retValue : integer;
value1, value2 : integer;
begin
value1 := strToInt(edFirstValue.Text);
value2 := strToInt(edSecondValue.Text);
{$IFDEF CPUX86}
_myDelphiPublicIntVariable := strToInt(edPublicIntVal.Text);
retValue := _addNumbers(value1, value2);
{$ELSE}
{$IFDEF CPUX64}
myDelphiPublicIntVariable := strToInt(edPublicIntVal.Text);
retValue := addNumbers(value1, value2);
{$ENDIF}
{$ENDIF}
showMessage('Sum is '+inttoStr(retValue));
end;
procedure TForm2.btShowMessageClick(Sender: TObject);
var
retVal : bigArray;
arrayLength : integer;
begin
arrayLength := length(retVal);
{$IFDEF CPUX86}
_myDelphiPublicStrVariable := edPublicStrVal.Text;
_cShowGetMessage(edCaption.Text, edMessage.Text, arrayLength, retVal);
{$ELSE}
{$IFDEF CPUX64}
myDelphiPublicStrVariable := edPublicStrVal.Text;
cShowGetMessage(edCaption.Text, edMessage.Text, arrayLength, retVal);
{$ENDIF}
{$ENDIF}
showMessage(retVal);
end;
function _MessageBoxW2(theHwnd:HWND; lpText : PWideCHAR;
lpCaption : PWideCHAR; uType:UINT):integer; cdecl;
begin
result := MessageBoxW(theHwnd, lpText, lpCaption, uType);
end;
end.
好的,這就是全部了。我不會在這裡展示 'C' 源文件,它們通常非常基礎,而在 Visual Studio 中需要完成的最重要任務是其配置(如我上面提到的)。仔細查看*vcxproj* 文件。
最後說明一下,在 VS 的*stdafx.h* 文件中,有一個#define DEBUGGING
。啟用它可以在 VS 中測試例程,禁用它則僅編譯*CtoDelphi.cpp* 文件。
历史
- 2011 年 10 月 4 日 - 初始版本發布。
- 2011 年 10 月 8 日 - 展示了如何從 Delphi 訪問 Visual Studio 公共變量,以及如何從 Visual Studio 訪問 Delphi 公共變量。