Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Using COFF C object files with Delphi X2

4.71/5 (7 votes)
8 Oct 2011CPOL4 min read 60.8K   831  
Still struggling to find the ultimate COFF to OMF converter to link some code with Delphi? You don't need to anymore.

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. 

CandDelphi1a.jpg

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. 

CandDelphi2a.jpg

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. 

CandDelphi3a.jpg

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

CandDelphi4a.jpg

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

CandDelphi5a.jpg 

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 the MessageBoxW 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. 

Pascal
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)