Using COFF C object files with Delphi X2

Using COFF C object files with Delphi X2

Introduction

People who develop software with Delphi and want to link extraneous object files are happy with Embarcadero for two reasons: the first one is because Embarcadero did not come up with any OMF (Object File Format) extension to use with the new 64-bit compiler, it just uses COFF. The second one is because the Delphi XE2 can also link with COFF object files when compiling for 32-bit (in addition to OMF). 

Background 

There are literally millions of lines of 'C' code waiting to be compiled and linked with Delphi in order to receive a nice GUI. I know that Embarcadero has a C++ Builder, but for pure (no GUI) C or C++ development, it stays behind Visual Studio in every respect. 

How can we use "C" code compiled with Visual Studio inside Delphi? Till now, it was nearly impossible because Visual Studio compiles to COFF, Delphi understood only OMF, and converters from COFF to OMF were not reliable because the OMF used by Delphi has many proprietary undocumented portions. 

I mentioned earlier that the latest Delphi release can read COFF object files, but I have not said that it is a piece of cake to actually link those object files with Delphi. 

So, is it easy or not to link with Delphi XE2 code compiled with Visual Studio? 

The answer is: "It is not difficult, just pay attention to a few details. It is even easier for 64-bit code."

 

Using the code

To show how it is done, let's open VS 2010, select New Project in the File menu, and go to the Visual C++ section. You can select the Empty Project on the General node, but is preferable to select either Win32 Console Application or Win32 Project on the Win32 Project node because this allows you to test the whole project in VS. After doing this, add a C++ file and a Header file from the Project menu. These two files are the ones where we are going to put the code that will compile and link with Delphi. In Solution Explorer, right-click the new C++ file you just added, select Properties, and in the sub-node Advanced of the C/C++ node, select Compile as C Code (/TC), and in the Precompiled Headers sub-node, select Not Using Precompiled Headers. Now, in the Project properties, General node, select Use Unicode Character Set. Disable all settings of the project that deal with exception handling to avoid complications (i.e., Enable C++ Exceptions="No" and Buffer Security Check = "No"). Finally, set Whole Program Optimization to "No" in the Optimization sub-node.

You are set and can start coding away your C functions. When done, right-click on the source file and select Compile. Do compilations for 32-bit and 64-bit, then copy the .obj files to the folder where you have the Delphi XE2 project.

The Delphi linker has neither information about the parameters of the functions you defined in VS2010 nor information about all the externals that need to be resolved at link time by the Delphi Linker. So, you need to declare all that in a Delphi unit. 

To link with 32-bit Delphi programs, the golden rules are:

  1. All the function declarations will be proceeded by an underscore and the calling convention should be cdecl (all right, this is the default).
  2. All the externals should have the cdecl calling spec; if it is a call to a Windows API, you must use in VS an intermediate function with a cdecl calling spec (see our demo program to see how we solved the call to theMessageBoxW API).
  3. Genuine externals with the cdecl calling spec are probably exports from msvcrt.dll and you can use this DLL from your Delphi program without the need to figure out replacement functions (see our demo program to see how).

To link VS object files with 64-bit Delphi XE2 programs, it is easier because:

  1. There is only one calling convention, fastcall.
  2. Function names are not underscored.
  3. Externals to the Windows API are directly resolved without our action.
  4. Using msvcrt.dll functions is just a question of declaring them in Delphi.

I provide a demo with full source code which you are recommended to download and study if you are not experienced in these matters. 

The Delphi source file is shown below. Notice all the points I mentioned earlier. For 32-bit: underscored functions, cdecl calling convention, direct use of msvcrt.dll exported functions, and use of an intermediate function in Visual Studio with the cdecl extension when a call to Windows API (stdcall) is to be made from Delphi.  

As you can see below, for 64-bit, it is easier mostly because there is only one calling convention. 

 Collapse | Copy Code

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.

All right, that's all about it. I won't show here the 'C' source files, they are pretty basic anyway and the most important task do be done in Visual Studio is its configuration (as I referred above). Have a careful look at the.vcxproj file. 

A final note, in VS stdafx.h file, there is a #define DEBUGGING. You enable it to test the routines in VS, you disable it when you compile just the CtoDelphi.cpp file.

History

  • Oct 4 2011 - Initial version release.
  • Oct 8 2011 - Shows how to access Visual Studio public variables from Delphi and Delphi public variables from Visual Studio.

窗体底端

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值