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++ |
| ShortInt | short
|
| Byte | BYTE
|
| char | unsigned short
|
| Integer | int
|
| Word | unsigned int
|
| LongInt | long
|
| Comp | unsigned long
|
| Single | float
|
| Real | None
|
| Double | double
|
| Extended | long double
|
| Char | char
|
| String | None
|
| pChar | char
|
| Bool | bool
|
| Boolean | Any 1byte type
|
| Variant | None
|
| Currency | None
|
|
Connect with Us