CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
Application Development with Borland C++ and
Borland |
Copyright 1996 Borland International, Inc. All rights reserved.
All
Borland product names are trademarks of Borland International, Inc.
Other brand and product names may be trade marks or registered trade marks of
their respective holders.
Table of Contents
Using Borland C++ and
Mechanisms for using C++ with
Delphi and
Benefits of DLLs: 2
Disadvantages of DLLs: 2
Benefits of statically linked OBJs: 2
Disadvantages of OBJs: 2
Using Calling Conventions to ensure protocols are compatible 3
Exporting C++ Library Routines 5
Functions to Access and manipulate C++ objects within a DLL 5
Using a C++ Object Instance in
a DLL from a
Calling a Delphi DLL from a C++ executable 13
Using a
C++ OBJ linked into a
Questions and answers 24
Conclusion 25
Table of C++ and
The purpose of this document is to familiarize you with ways
of using your existing C/C++ code in Delphi and your existing
The 1990's have proved to be a booming decade in the PC
industry. With the growth of the PC market comes the growth of demands for
programs that run on them. With this demand comes the need for more developers,
better tools and better platforms on which these programs run. With the release
of new development tools such as Borland's
By now, there are millions of lines of C/C++ code in
existence which equates to billions of dollars for development. It is usually
impractical to port large bodies of code from one language to another.
Furthermore, you may have already just completed a large port -- from Pascal to
16 bit Delphi and then to 32 bit
Note: This document
assumes you are using
There are three ways to integrate C++ and
You should choose whichever mechanism is most suitable to your program's implementation. Knowledge of the pros and cons of using DLLs or OBJs will assist in your selection.
Smaller EXE footprint: The DLL is compiled separately from your main EXE and linked in dynamically at run-time. Because of this, it does not increase the size of your calling EXE.
Easy to maintain: The code is encapsulated in its own module (the DLL). Updating this portion of the code no matter how large is as simple as sending your customers the changed DLL. Of course, with DLLs, you do need to be concerned with versioning issues between the EXE and DLLs.
Speeds development time: A 'build all' of your main program source won't include the DLL source as it is compiled and linked separately.
Shared Code means global revisions: Multiple applications can call the same DLL file. Fixing a bug in the DLL file fixes the same bug in all applications that use it.
Another file to distribute: The calling process must be able to find the file. Also, more files usually means more documentation.
Slower startup: The DLL is loaded separately and mapped into the loading process' address space. This means another Kernel file-mapping object must be created and the DLL must be sought out. Once the application has loaded, however, DLLs don't appreciably affect performance.
More room for programmatic errors: More allowances and considerations must be made programmatically as the loading module must match the calling conventions of the DLL and prevent C++ name mangling, (this is also an issue with OBJ), and the location of the DLL must be known.
Fast runtime execution: Because there are no external calls to make.
Fewer modules to distribute: Everything is already linked into one single executable
Large executable: The modules are linked in statically.
Can't share among multiple EXEs: Statically linked code becomes part of whatever EXE linked it in. Therefore, if code could potentially be shared among multiple EXEs, depending on the size/performance tradeoff, a DLL might be more appropriate.
More room for programmatic errors: More allowances and considerations must be made progammatically as the OBJ routines must match the calling conventions of the routine import declarations in the executeable using it.
Whichever mechanism is best for you, some additional work is still required as described a little later in this document.
Whether or not you're using DLLs or OBJs, it is important to ensure that the code written in one language is generated following the protocol of the other.
The calling convention used defines important low level aspects of a program's behavior. It determines the order in which parameters passed to functions are put on the stack. It determines if registers share the responsibility with the stack of holding passed parameters. It determines who is responsible for maintaining the stack i.e., whether the calling function or the called function cleans up the stack. In the case of C++, modifiers can be used to specify whether or not the symbol names get mangled.
Failure to correctly match calling conventions will result in fatal program errors and/or erratic program behavior and results.
Windows exports functions in its Application Programming
Interfaces (API) using a calling convention different from
The calling convention used for exported functions in the
Win32 API is called stdcall. A method or function following the stdcall calling
convention requires parameters to be passed to it on the stack in right to left
order. The called routine is responsible for cleaning up the stack. In C++ use
the directive _stdcall. In
The following declares the same exported function in C++ and
extern 'C'
int _stdcall _export TestStdCall();
function TestStdCall: integer; StdCall;
export;
The default calling convention used by Borland C++ is called cdecl. A method or function following the cdecl calling convention, like stdcall, requires parameters to be passed to it on the stack in right to left order. The difference from stdcall is in that the calling routine is responsible for cleaning up the stack. This convention is unique in that it supports the passing of a variable number of parameters. This type of support is available as result of the right to left parameter ordering.
In C++ use the directive _cdecl to explicitly declare a
routine as cdecl although this is redundant as _cdecl is the default. In
Note that although Delphi supports the cdecl calling
convention in terms of parameter ordering and stack maintenance,
The following declares the same exported function in C++ and
extern 'C'
int _cdecl _export TestCDecl();
function TestCDecl: integer; CDecl;
export;
The default calling convention used by 16 bit
The following declares the same exported function in C++ and
extern 'C'
int _pascal _export TestPascal();
function TestPascal: integer; Pascal;
export;
The default calling convention used by
The following declares the same exported function in C++ and
extern 'C'
int _fastcall _export TestFastCall();
function TestFastCall: integer;
Register; export;
Note: C++ uses
name-mangling.
Suppose you have existing code written in C++ that you wish
to access from your
//declaration
extern 'C' BOOL _stdcall _export IsTodayFriday();
//definition
extern 'C' BOOL _stdcall _export IsTodayFriday()
If the routine IsTodayFriday were part of a function library whose prototype you would rather avoid modifying, you could export an access function to call it instead:
//declaration
extern 'C' BOOL _stdcall
_export AccessIsTodayFriday();
//definition
extern 'C' BOOL _stdcall _export AccessIsTodayFriday ()
It is really up to you to decide to use access functions or
directly export your existing library functions themselves. Either way you have
to write some code. However, the amount of code is far less than what would be
required to rewrite the routines in one language or another. Again note that
You can create and export access functions from a DLL that
operate on objects instantiated within the DLL. This is often necessary when a
Suppose you have a C++ class called TMyObject which has 3 AddToField member functions. Each AddToField member function expects a different parameter type and is therefore overloaded. Each AddToField member function writes to the correct field in a table based on the data type of the passed parameter. Consider the following declaration for TMyObject:
class TMyObject ;
Since
//declarations
extern 'C' BOOL _stdcall
_export AddToField_LI(long,TMyObject* obj);
extern 'C' BOOL _stdcall
_export AddToField_STR(char*,TMyObject* obj);
extern 'C' BOOL _stdcall
_export AddToField_BOOL(BOOL,TMyObject* obj);
//definitions
BOOL _stdcall _export
AddToField_LI(long liVal, TMyObject* obj)
It is really up to you to decide to use
acce BOOL _stdcall _export
AddToField_STR(char* sBuf,TMyObject* obj)
BOOL _stdcall _export
AddToField_BOOL(BOOL bTorF,TMyObject* obj)
Above, 3 access functions are exported. Each has a slightly different name and a different data type in the first required parameter. The body of these functions call the appropriate AddToField method of the TMyObject object passed as a pointer in the second parameter. The access functions act as a wrapper for the overloaded AddToField methods.
Given the previous example, you are again left with a decision: should you rewrite your classes to not overload methods or should you take the time to write access functions? Keep in mind that any of your programs that use your existing classes would have to be changed if you choose to modify the overloaded functions themselves.
Delphi 2.0 and
The vtable/VMT holds the addresses of an object's methods
all the way up the class hierarchy. It is by this mechanism that the correct
method gets executed even if it has been overridden by several sibling classes.
Because both
The layout and representation of the fields within a
In order to use an object in
class TMyObject ;
extern 'C' TMyObject*
_stdcall _export InitObject();
extern 'C' void _stdcall
_export UnInitObject(TMyobject*);
The class TMyObject is declared first. You may either use an existing definition of a C++ class with virtual functions, or you may create a wrapper class that exposes the functionality of your object through virtual methods. If you use an existing class definition, be aware that you can only access it's virtual functions and not its member data. Also, be sure that your class uses only single inheritance.
The two functions InitObject and UnInitObject are declared
with the extern 'C' directive in order to ensure that C++ name
mangling doesn't alter the names of these functions this is necessary because
Delphi doesn't understand name mangling. If these wrappers are to be contained
within a C++ DLL (as opposed to being linked from an OBJ), you will need to
export these functions with the _export keyword. Further, these functions and
all virtual functions are declared using the _stdcall calling convention. The
calling conventions must match on both the
The definitions of the wrapper functions are provided below and we'll see the code for our object later on:
//Definitions
extern 'C' TMyObject*
_stdcall _export InitObject()
extern 'C' void _stdcall
_export UnInitObject(TMyObject* obj)
The InitObject function creates a TMyObject object using the
new operator and returns this pointer. When your
Although this appears straightforward on the C++ side, the
type
TMyObject = class
function VTOpenTable(pcTableName:
PChar): integer; virtual;
stdcall; abstract;
function VTDeleteRecord(iRecNo:
integer): integer; virtual;
stdcall; abstract;
function VTCloseTable: integer;
virtual; stdcall; abstract;
end;
The calling conventions on the object's methods (stdcall) must match that specified in the C++ program. They are also declared virtual and abstract.
function InitObject: TMyObject;
stdcall; far; external 'testobj.dll'
name 'InitObject';
procedure UnInitObject(obj: TMyObject);
stdcall; far; external
'testobj.dll'
name 'UnInitObject';
The example uses the stdcall calling convention and the
methods are virtual. Let's take a look at the complete source to both the
Here is the C++ code which is compiled into a DLL.
#include <WINDOWS.H>
// TMyObject Class Declaration
class TMyObject ;
//TMyObject Constructor
TMyObject::TMyObject()
//VTOpenTable Method hypothetically
would open a table.
int _stdcall
TMyObject::VTOpenTable(char* sTableName)
//VTDeleteRecord Method hypothetically
would delete a record.
int _stdcall
TMyObject::VTDeleteRecord(int i)
//VTCloseTable Method hypothetically
would close a table.
int _stdcall TMyObject::VTCloseTable()
#ifdef __cplusplus
extern 'C'
//Release Object Instance-Access
Function
void _stdcall _export
UnInitObject(TMyObject * obj)
#ifdef __cplusplus
}
#endif
Notice the extern 'C' which is wrapped around
the definitions of the InitObject and UnInitObject global functions. This is
necessary to ensure that C++ name mangling is disabled so that these functions
can be imported into the
The
unit Testobj;
interface
uses
Windows, Messages, SysUtils, Classes,
Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender:
TObject);
procedure FormClose(Sender:
TObject; var Action: TCloseAction);
private
public
end;
var Form1: TForm1;
implementation
type
TMyObject = class
function VTOpenTable(pcTableName:
PChar): integer; virtual;
stdcall; abstract;
function VTDeleteRecord(iRecNo:
integer): integer; virtual;
stdcall; abstract;
function VTCloseTable: integer;
virtual; stdcall; abstract;
end;
function InitObject: TMyObject;
stdcall; far; external 'testobj.dll'
name 'InitObject';
procedure UnInitObject(obj: TMyObject);
stdcall; far; external
'testobj.dll'
name 'UnInitObject';
procedure TForm1.Button1Click(Sender:
TObject);
var obj: TMyObject;
begin
obj := InitObject;
obj.VTOpenTable('PARTNO.DB');
obj.VTDeleteRecord(10);
obj.VTCloseTable;
UnInitObject(obj);
end;
procedure TForm1.FormClose(Sender:
TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
If you don't want to use a C++ DLL, but would rather link
your C++ obj files directly into your Delphi application, you need to specify
the C++ obj file with the following directive in the
In addition, instead of importing the C++ wrapper functions
into the
function InitObject:
TMyObject; stdcall; far; external;
procedure UnInitObject(obj: TMyObject);
stdcall; far; external;
By following some simple conventions and rules, you can
easily integrate C++ code into your
Your global functions and (member functions) must use match ing calling conventions (stdcall was used in our example)
Your classes must declare and define virtual functions in C++ and only use single inheritance.
Your classes must be declared in
You must use extern 'C' on the C++
side to disable name mangling for any functions imported into the
You must use wrapper functions to wrap the C++
new and delete operators. These are imported by declaring external on the
A DLL written in
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
//Use IMPLIB.EXE against DDLL.DLL to
generate a lib file which
//should be added to this example's
project.
#include <WINDOWS.H>
#define IDC_PUSHBUTTON1 101
// C++ uses name-mangling.
functions
// exported from
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)
break;
}
return FALSE;
}
//Program entry-has all the typical
Windows stuff
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int
nCmdShow)
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
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
//Use IMPLIB.EXE against DDLL.DLL to
generate a lib file which
//Should be included in this example's
project.
#include <WINDOWS.H>
#define IDC_PUSHBUTTON1 101
// C++ uses name-mangling.
functions
// exported from
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 ;
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)
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
Note: Once you build
the
The following example shows a C++ OBJ linked into a
// COBJ Example
// This is an example of an OBJ created
with Borland C++ that is linked
// into an EXE (DAPP.EXE) created with
#include <WINDOWS.H>
//Declaration
extern 'C' void _stdcall COBJ_Function();
void _stdcall COBJ_Function()
The
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
public
end;
var
Main: TMain;
implementation
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:
procedure COBJ_Function; StdCall; far;
external;
The
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
//Make sure you add DOBJ.OBJ to the
project before linking.
#include <WINDOWS.H>;
#define IDC_PUSHBUTTON1 101
extern 'C'
static HINSTANCE hInst;
#pragma argsused
LONG FAR PASCAL MainDialogProc(HWND
hWnd, WORD wMsg, WORD
wParam, LONG lParam)
break;
}
return FALSE;
}
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
The example executes the function Delphi_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'
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
Note: Many Windows API
calls resolve to functions of other names. In C++, this is usually handled by
the preprocessor. Since this
unit dobj;
interface
uses windows;
procedure Delphi_Function; StdCall;
implementation
procedure Delphi_Function; StdCall;
begin
MessageBoxA(0, 'Hello from a
'Success', MB_OK or
MB_TASKMODAL);
end;
begin
end.
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
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 * .
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.
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.
|
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 |
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1284
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved