65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (7投票s)

2011年10月5日

CPOL

4分钟阅读

viewsIcon

61962

downloadIcon

853

仍在為尋找終極的 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。

CandDelphi1a.jpg

我們如何在 Delphi 中使用 Visual Studio 編譯的 "C" 代碼?到目前為止,這幾乎是不可能的,因為 Visual Studio 編譯為 COFF,而 Delphi 只懂 OMF,從 COFF 到 OMF 的轉換器並不可靠,因為 Delphi 使用的 OMF 有許多專有且未記錄的部分。

CandDelphi2a.jpg

我之前提到過,最新的 Delphi 版本可以讀取 COFF 對象文件,但我沒有說實際將這些對象文件與 Delphi 鏈接是一件容易的事。

CandDelphi3a.jpg

那麼,將 Visual Studio 編譯的代碼鏈接 Delphi XE2 是容易還是不容易?

CandDelphi4a.jpg

答案是:“不難,只需注意一些細節。對於 64 位代碼甚至更容易。”

CandDelphi5a.jpg 

使用代码

為了展示如何操作,讓我們打開 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 程序,黃金法則如下:

  1. 所有函數聲明都將以底線開頭,並且調用約定應為 cdecl(好吧,這是默認的)。
  2. 所有外部引用都應具有 cdecl 調用規範;如果調用 Windows API,您必須在 VS 中使用具有 cdecl 調用規範的中間函數(請參閱我們的演示程序,了解我們如何解決對MessageBoxW API 的調用)。
  3. 帶有 cdecl 調用規範的 Genuine 外部引用可能是*msvcrt.dll* 的導出,您可以從 Delphi 程序中使用此 DLL,而無需找出替換函數(請參閱我們的演示程序了解如何操作)。

鏈接 VS 對象文件與 64 位 Delphi XE2 程序更容易,因為:

  1. 只有一種調用約定,即 fastcall。
  2. 函數名稱沒有底線。
  3. 對 Windows API 的外部引用無需我們操作即可直接解析。
  4. 使用*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 公共變量。
© . All rights reserved.