Delphi 2 - Application Development with Borland C++ and Delphi

Abstract: The purpose of this document is to familiarize you with ways of using your existing C/C++ code in Delphi and your existing Delphi code in C++.

Using Borland's Delphi and C++
A Technical paper for developerA Technical paper for developers
Alain Tadros, with additions by Eric Uber
    NOTE: The views and information expressed in this document represent those of its author(s) who is solely responsible for its content. Inprise does not make or give any representation or warranty with respect such content.

Calling a Delphi DLL from a C++ executable
A DLL written in Delphi can be loaded and its exported functions called from an executable written in C++. Again note that the calling conventions for each function must be consistent across both languages. The following shows the source code to a DLL called DDLL.DLL written using Delphi 2.0:

 
library DDLL;

uses Windows;

function GetDelphiString: PChar; StdCall;
begin
MessageBox(0, 'Click OK and GetDelphiString will return a string!', 
'Info',
	  MB_OK or MB_TASKMODAL);
result := PChar('This is a string passed from a Delphi DLL');
end;

exports
GetDelphiString;

begin
end.

DLL.DLL exports a single functions called GetDelphiString. GetDelphiString Displays a message dialog box then returns a PChar string after the user clicks OK. A PChar is the C++ compliment of a NULL terminated character buffer. See the section at the end of this document called "Table of C++ and Delphi data types" for a listing of Delphi and C++ type comparisons. The following is the C++ code which uses DDLL.DLL and accesses the GetDelphiString function:

//Use IMPLIB.EXE against DDLL.DLL to generate a lib file which
//should be added to this example's project.
#include 
#define IDC_PUSHBUTTON1 101

// C++ uses name-mangling.  Delphi does not.  Use extern "C" for all 
functions
// exported from Delphi DLLs to indicate that the function is not 
name-mangled.
// When compiling in C, rather than C++, extern "C" is not required.  
See the
// Borland C++ on-line help for more information on extern "C" and 
name-mangling.

extern "C" char*  _stdcall GetDelphiString();

// Globals
static HINSTANCE hInst;
char* StringPassed;

// This example assumes you have a Dialog resource called "MAINDIALOG".

// The resource has 3 push buttons: IDOK,IDCANCEL and IDC_PUSHBUTTON1.
#pragma argsused
LONG FAR PASCAL MainDialogProc(HWND hWnd, WORD wMsg, 
WORDwParam, LONG lParam)
{
switch(wMsg)
{   case WM_INITDIALOG:
      return TRUE;
  case WM_COMMAND:
      switch(wParam)
      {

      // Button was pushed, so call Delphi function
      case IDC_PUSHBUTTON1:
	    StringPassed = GetDelphiString();
	    MessageBox(NULL, StringPassed,  "Success", MB_OK |  
MB_TASKMODAL);
	    return TRUE;

      // Ok or cancel, so end the dialog
      case IDOK:
      case IDCANCEL:
	     EndDialog(hWnd, 0);
	     return TRUE;
      }
      break;
}
return FALSE;
}

//Program entry-has all the typical Windows stuff
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
				  LPSTR lpszCmdLine, int 
nCmdShow)
{ // Save the instance.
hInst = hInstance;

// Load the dialog box.
If ( DialogBox(hInstance, "MAINDIALOG", NULL, 
(FARPROC)MainDialogProc) 
== -1 )
  MessageBox(NULL, "Can't load dialog box!n", NULL, MB_OK | 
MB_APPLMODAL);
return 0;
}

The above executable simply prototypes the GetDelphiString function. The stdcall convention is used which matches the export from DDLL.DLL. The prototype uses extern "C" to prevent name mangling. Notice the return value is char*. This is a C++ compliment to Delphi's PChar type. Using a Delphi Object Instance in a DLL from a C++ EXE
Earlier in this document ("Using a C++ Object Instance in a DLL from a Delphi EXE") the issue of using a class in a C++ DLL from a Delphi executable was discussed. How about the reverse scenario? Using the C++ executable code sample above, make the following modifications:

 
//Use IMPLIB.EXE against DDLL.DLL to generate a lib file which
//Should be included in this example's project.
#include 
#define IDC_PUSHBUTTON1 101

// C++ uses name-mangling.  Delphi does not.  Use extern "C" for all 
functions
// exported from Delphi DLLs to indicate that the function is not 
name-mangled.
// When compiling in C, rather than C++, extern "C" is not required.  
See the
// Borland C++ on-line help for more information on extern "C" and 
name-mangling.

class TMyObject   {
public:
  virtual int _stdcall VTOpenTable(char* sTableName) = 0;
  virtual int _stdcall VTDeleteRecord(int iRecNo) = 0;
  virtual int _stdcall VTCloseTable() = 0;
};

extern "C" TMyObject* _stdcall InitObject();


extern "C" void _stdcall DeInitObject(TMyObject* oVertObj);

// Globals
static HINSTANCE hInst;

Really the only change thus far is in the removal of the declaration for GetDelphiString. It is instead replaced with the declaration of the class TMyObject. Notice the methods are all declared virtual and abstract. The calling convention chosen is stdcall. One more section of the code still requires modification as follows:

// Globals
static HINSTANCE hInst;

// This example assumes you have a Dialog resource called "MAINDIALOG".

// The resource has 3 push buttons: IDOK,IDCANCEL and IDC_PUSHBUTTON1.
#pragma argsused
LONG FAR PASCAL MainDialogProc(HWND hWnd, WORD wMsg, 
WORDwParam, LONG lParam)
{
switch(wMsg)
{   case WM_INITDIALOG:
      return TRUE;
 case WM_COMMAND:
      switch(wParam)
      {

      // Button was pushed, so call Delphi function
      case IDC_PUSHBUTTON1:
      {
TMyObject* obj = InitObject();
obj->VTOpenTable("CUSTMAIN.DB");
obj->VTDeleteRecord(10);
obj->VTCloseTable();
UnInitObject(obj);
	  return TRUE;
      }

Here, the global declaration for char* StringPassed; was removed. Also, the code block following the case IDC_PUSHBUTTON1 has been modified. The new code block declares a pointer to an object of type TMyObject as it will be defined in the Delphi DLL. The InitObject function is called from the Delphi DLL which creates and returns an instance of TMyObject. Each TMyObject method is then called. The instance is released by passing it back to the Delphi DLL via the UnInitObject function. The following is the code for the Delphi DLL:

library DDLL;

uses Windows, Dialogs;

type
TMyObject = class
function VTOpenTable(pcTableName: PChar): integer; virtual; 
stdcall;
function VTDeleteRecord(iRecNo: integer): integer; virtual; 
stdcall;
function VTCloseTable: integer; virtual; stdcall;
end;

function TMyObject.VTOpenTable(pcTableName: PChar): integer; stdcall;

begin
ShowMessage('VTOpenTable');
end;

function TMyObject.VTDeleteRecord(iRecNo: integer): integer;  
stdcall;
begin
ShowMessage('VTDeleteRecord');
end;

function TMyObject.VTCloseTable: integer;  stdcall;
begin
ShowMessage('VTCloseTable');
end;

function InitObject: TMyObject; StdCall;
var oVertObj: TMyObject;
begin
oVertObj := TMyObject.Create;
result := oVertObj
end;

procedure UnInitObject(oVertObj: TMyObject); StdCall;
begin
oVertObj.Free;
end;


exports
InitObject, UnInitObject;

begin
end.

The code for the Delphi DLL is rather simple. It exports the access functions InitObject and UnInitObject. These are declared using the directive StdCall. The type section of the unit declares the actual TMyObject class. Its methods are all virtual following the stdcall calling convention. The actual method bodies don't do much, they simply acknowledge that they were actually called via a call to Delphi's ShowMessage function.

Note: Once you build the Delphi DLL, you should re-run the IMPLIB.EXE utility so that the resulting .LIB file includes the InitObject and UnInitObject exports. The .LIB file should be compiled into the C++ executable.

C++ OBJ linked into a Delphi Executable
The following example shows a C++ OBJ linked into a Delphi application. The code is simplistic in content as it exports a single function called COBJ_Function. The function does not accept any parameters and returns nothing. The function body simply calls the Windows API function MessageBox which will be displayed when called from the loading Delphi program. Here is the C++ source code:

  
// COBJ Example
// This is an example of an OBJ created with Borland C++ that is linked

// into an EXE (DAPP.EXE) created with Delphi.
#include 

//Declaration
extern "C"   void _stdcall COBJ_Function();

void _stdcall COBJ_Function()
{
MessageBox(NULL,  "Hello from a Borland C++ OBJ!",
	"Success", MB_OK | MB_TASKMODAL);
return;
}

The Delphi code is also rather simplistic. The following Delphi unit can be used in an application to link in the C++ OBJ and make use of the function COBJ_Function().

 
unit DAPPMAIN;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
Dialogs,
StdCtrls;

type
TMain = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Main: TMain;

implementation

{$R *.DFM}

{Specify the name of the OBJ containing the function.}
{$L cobj.obj}

procedure COBJ_Function; StdCall; far; external;

procedure TMain.Button1Click(Sender: TObject);
begin
COBJ_Function;
end;

end.

The compiler directive following the units implementation section really is what is of interest:

{$L cobj.obj}

Delphi doesn't have a pre-processor like in C++: therefore it makes use of compiler directives. To link a .OBJ file into a Delphi application, use the $L compiler directive followed by the name of the .OBJ file to bind in. To use a function in a .OBJ file, the function must be declared in Delphi as external. Notice that the StdCall calling convention is used as well as far and external.

 
procedure COBJ_Function; StdCall; far; external;

The Delphi unit shown simply calls the COBJ_Funtion when the OnClick event for Button1 occurs.

Delphi OBJ Linked into a C++ Executable
The following example shows how to link in a Delphi OBJ into a C++ application. Here is the code for the C++ executable:

// CAPP Example
// This is an example of an EXE created with Borland C++ that calls a
// function that resides in an OBJ (DOBJ.OBJ) created with Delphi.
//Make sure you add DOBJ.OBJ to the project before linking.

#include ;
#define IDC_PUSHBUTTON1 101


extern "C" {
  void _stdcall Delphi_Function();
}

static HINSTANCE hInst;

#pragma argsused
LONG FAR PASCAL MainDialogProc(HWND hWnd, WORD wMsg, WORD
wParam, LONG lParam)
{
switch(wMsg)
{
 case WM_INITDIALOG:
      return TRUE;

 case WM_COMMAND:
      switch(wParam)
      {

      // button was pushed, so call Delphi function
      case IDC_PUSHBUTTON1:
	     Delphi_Function();
	     return TRUE;

      // ok or cancel, so end the dialog
      case IDOK:
      case IDCANCEL:
	     EndDialog(hWnd, 0);
	     return TRUE;
      }
      break;
}
return FALSE;
}

#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
			       LPSTR lpszCmdLine, int nCmdShow)
{
// save the instance
hInst = hInstance;

// load the dialog box
if(DialogBox(hInstance, "MAINDIALOG", NULL,
(FARPROC)MainDialogProc) == -1 )
 MessageBox(NULL, "Can't load dialog box!n", NULL,
MB_OK | MB_TASKMODAL);

return 0;
}

The example executes the function Delpi_Function which is actually defined in an OBJ. file called DOBJ.OBJ. In order to use this function, it is declared at the top of the code listing as

  
extern "C" {
  void _stdcall Delphi_Function();
}

Note that extern "C" is used and _stdcall as the calling convention. When the programs dialog box window procedure receives _IDC_PUSHBUTTON1 in the wParam of the WM_COMMAND message, the function Delpi_Function is called.

Take a look at the following code listing. It is the Delphi OBJ source code for the OBJ that gets linked into the C++ executable shown above. It's important to enable Project | Options | Linker | Generate Object files before compiling this example. (The default in Delphi is NOT to generate OBJ files.)

Note: Many Windows API calls resolve to functions of other names. In C++, this is usually handled by the preprocessor. Since this Delphi code never goes through the C++ preprocessor, you must call the actual function name directly.

unit dobj;
{
DOBJ Example
This is an example of an OBJ created with Delphi that is linked into
an EXE created with Borland C++.  It's important to enable
Project | Options | Linker | Generate Object files.
}

interface

uses windows;

procedure Delphi_Function; StdCall;

implementation

procedure Delphi_Function; StdCall;
begin
{ Many Windows API calls resolve to functions of other names.  In C++,
this is usually handled by the preprocessor.  Since this Delphi code
never goes through the C++ preprocessor, you must call the actual
function name directly. Below, MessageBoxA is called instead of
MessageBox.
}

MessageBoxA(0, 'Hello from a Delphi OBJ!',
	  'Success', MB_OK or MB_TASKMODAL);
end;

begin
end.

Questions and Answers

Question: Can I use any RTL functions in either language to create usable OBJs?

Answer: Yes and no.
No, because all the RTL functions get linked during Link time, not compile time; it's going to be the other language's responsibility to resolve those functions.
Yes, because you can make an OBJ out of the RTL functions and include it in the project with your other OBJ file. This way, though, is unrealistic due to the size of OBJ created for the RTL functions. This will make your executable much, much larger, and is probably not a good choice.

Question: What utilities can I use to facilitate mixing the 2 languages?

Answer: Borland ships 2 utilities with its language products that can be very helpful in that area, IMPLIB and TDUMP.
IMPLIB is a utility which converts a DLL file into a LIB file. You can then plug this LIB file into your project immediately. For more explanation and to see the different arguments, just type implib at the command line and press Enter.
TDUMP is a great utility and you should consider it your friend. It will tell you everything you need to know about your DLL, LIB or EXE files from the internals stand point: name mangling, exports, imports, library definitions, code- and data-segments, memory layout, etc.. You can check for function-name mangling, and can find the internal names of functions you want to use. You can explore the differences between calling conventions, by watching the function names change every time you change the calling convention.

For more information on TDUMP and its arguments, type tdump at the command line, and press Enter.

Question: I bought a Delphi library that contains a function that returns a STRING, how can I use this function in C++ although I have no access to the Delphi code?

Answer: There is no immediate way to use the returned STRING value in C or C++. The only way is to create another Delphi DLL that will take the String result as a parameter and return a pCHAR which will be usable in C or C++ as a CHAR * .

Conclusion
In summary, Borland Delphi 2.0 and Borland C++ 5.0 use essentially the same compiler back end. This provides a useful level of compatibility on the lowest levels. Delphi applications can link in Borland C++ OBJ files and Borland C++ applications can link in Delphi OBJ files. Because the Virtual Table (called the vtable in C++, VMT in Delphi) format is the same, the methods of an object instantiated in a DLL written in one language (specific to Borland Delphi 2.0 or Borland C++ 5.0) can be accessed from another. The Borland Delphi and Borland C++ languages provide directives which support the standard calling conventions Cdecl, Stdcall, Fast-call and Pascal.

Given the information in this document, you should now be able to conclude which method of code sharing is best for you. It is unfortunate that additional work is needed for all of the methods discussed, however, it is great that Borland provides these mechanisms as the additional work is minimal compared to that which would be required to completely re-write all of your existing code in one language or another.

Table of C++ and Delphi data types

Delphi C/C++
ShortIntshort
ByteBYTE
charunsigned short
Integerint
Wordunsigned int
LongIntlong
Compunsigned long
Singlefloat
RealNone
Doubledouble
Extended long double
Charchar
StringNone
pCharchar
Boolbool
BooleanAny 1byte type
VariantNone
CurrencyNone

Server Response from: ETNASC04