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:
- All the function declarations will be proceeded by an underscore and the calling convention should be cdecl (all right, this is the default).
- 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). - 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:
- There is only one calling convention, fastcall.
- Function names are not underscored.
- Externals to the Windows API are directly resolved without our action.
- 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.
unit VsAndDelphi;
interface
-> Requiires Delphi XE2 or later
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
public
end;
var
Form2: TForm2;
type
bigarray = array[0..127] of char;
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;
procedure _publicCInteger;external;
procedure _publicCArray;external;
var
_myDelphiPublicIntVariable : integer;
_myDelphiPublicStrVariable : string;
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';
procedure publicCInteger;external;
procedure publicCArray;external;
var
myDelphiPublicIntVariable : integer;
myDelphiPublicStrVariable : string;
implementation
procedure TForm2.btGeetCVarsClick(Sender: TObject);
var
myCInt : integer;
begin
myCInt := integer((@_publicCInteger)^);
showMessage(inttostr(myCInt));
myCInt := integer((@publicCInteger)^);
showMessage(inttostr(myCInt));
end;
procedure TForm2.btGetStringClick(Sender: TObject);
var
myCArray : pchar;
begin
myCArray := pchar((@_publicCArray)^);
showMessage(myCArray);
myCArray := pchar((@publicCArray)^);
showMessage(myCArray);
end;
procedure TForm2.btAddValuesClick(Sender: TObject);
var
retValue : integer;
value1, value2 : integer;
begin
value1 := strToInt(edFirstValue.Text);
value2 := strToInt(edSecondValue.Text);
_myDelphiPublicIntVariable := strToInt(edPublicIntVal.Text);
retValue := _addNumbers(value1, value2);
myDelphiPublicIntVariable := strToInt(edPublicIntVal.Text);
retValue := addNumbers(value1, value2);
showMessage('Sum is '+inttoStr(retValue));
end;
procedure TForm2.btShowMessageClick(Sender: TObject);
var
retVal : bigArray;
arrayLength : integer;
begin
arrayLength := length(retVal);
_myDelphiPublicStrVariable := edPublicStrVal.Text;
_cShowGetMessage(edCaption.Text, edMessage.Text, arrayLength, retVal);
myDelphiPublicStrVariable := edPublicStrVal.Text;
cShowGetMessage(edCaption.Text, edMessage.Text, arrayLength, retVal);
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.