在软件开发中,C语言因其性能和广泛的应用而备受青睐。Delphi,作为一种面向对象的编程语言,提供了丰富的库和组件,非常适合开发具有图形用户界面(GUI)的应用程序。然而,将Visual Studio编译的C代码与Delphi项目链接一直是一个挑战,因为两者使用的编译目标文件格式不同。本文将介绍如何克服这一障碍,实现两者的无缝集成。
尽管Embarcadero提供了C++ Builder,但在纯C或C++开发方面,它仍然落后于Visual Studio。幸运的是,最新的Delphi版本支持读取COFF对象文件,这为将Visual Studio编译的C代码与Delphi项目链接提供了可能。
首先,确保已经安装了Visual Studio和Delphi XE2或更高版本。接下来,将创建一个Visual Studio项目,编写C代码,并将其编译为对象文件。然后,将这些对象文件与Delphi项目链接。
打开Visual Studio 2010,选择“文件”菜单中的“新建项目”,然后选择“Visual C++”部分。建议选择“Win32控制台应用程序”或“Win32项目”,这样可以在Visual Studio中测试整个项目。
接下来,添加一个C++文件和一个头文件。这两个文件将包含将要编译并与Delphi链接的代码。在“解决方案资源管理器”中,右键单击新添加的C++文件,选择“属性”,在“高级”子节点下,选择“编译为C代码(/TC)”,并在“预编译头文件”子节点下,选择“不使用预编译头文件”。
在项目属性的“常规”节点下,选择“使用Unicode字符集”。禁用所有与异常处理相关的设置,以避免复杂性(例如,将“启用C++异常”设置为“No”,将“缓冲区安全检查”设置为“No”)。最后,在“优化”子节点下,将“全程序优化”设置为“No”。
现在,可以开始编写C函数了。完成后,右键单击源文件并选择“编译”。为32位和64位编译,然后将.obj文件复制到DelphiXE2项目的文件夹中。
Delphi链接器没有关于在Visual Studio 2010中定义的函数参数的信息,也没有关于Delphi链接器在链接时需要解析的所有外部引用的信息。因此,需要在Delphi单元中声明所有这些信息。
对于32位Delphi程序,黄金规则是:所有函数声明前都要加上下划线,调用约定应为cdecl(默认情况下是这样)。所有外部引用都应具有cdecl调用规范;如果是调用Windows API,必须在Visual Studio中使用具有cdecl调用规范的中间函数(请参阅示例程序,了解如何解决MessageBoxW API的调用)。
对于64位Delphi XE2程序,链接Visual Studio对象文件更容易,因为:只有一个调用约定,即fastcall。函数名不加下划线。直接解析Windows API的外部引用,无需采取行动。
以下是一个Delphi源文件的示例。请注意之前提到的所有要点。对于32位:下划线函数,cdecl调用约定,直接使用msvcrt.dll导出的函数,以及在Visual Studio中使用cdecl扩展的中间函数,以便从Delphi调用Windows API(stdcall)。
对于64位,它更容易,主要是因为只有一个调用约定。
unit VsAndDelphi;
interface
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.