Converting from C++ to Object Pascal/Delphi!
==============================
Topics covered (in order):
Overview
Data types
Keywords
Statement terminators
Declaring variables
Strings
Arrays
Assigning and comparing values
Declaring constants
Functions and procedures
The with ... do statement in OP
Commenting
Controlling program flow
Object oriented structures
Containers
Exception handling
Streaming
Arrangement of project files
How to make the conversion
Summary
Stuff not covered
=================================
Overview:
The purpose of this article is to help you understand the differences and
similarities between C++ and Object Pascal (the language used in Borland's Delphi
development tool), and to assist you in converting a project from C++ to Delphi.
It was originally prepared as a lecture to the Windows Programming SIG (Special
Interest Group) of the VPCUS (Vancouver PC User's Society).
Throughout this article, Object Pascal will be referred to as "OP".
We recently converted a C++ project (Komodo Market) to Delphi to avoid the
frustration and complexity of C++ interface construction. (Komodo Market is a complete
stock and option trading simulation. It is edutainment, both a game and a realistic
simulation where you can learn about stock, stock option, and index option trading.)
Delphi was not available when we started this project, and we did not want a multi-file,
interpreted language executable requiring a runtime. Since we already knew C++, it made
sense at the time to use it.
I have written a lot of programs in C++, but its usefulness is hampered by the lack
of visual development tools. When you look at Komodo Market, think to yourself, "Would
this have been fun to code in C++!?". Right.
This article deals with developing windows programs that have nothing at all to do
with Delphi's database development capabilities (which are superb), but the language
features I discuss are applicable. (Would you ever consider developing a database
application in C++? See a doctor.)
=================================
Data types
The following chart will helps to map C++ data types to OP data types. When
declaring variables, use this chart to cross-reference the conversion.
C++ OP Size (bytes) Range
unsigned char byte 1 0 to 255
unsigned int word 2 0 to 65,535
unsigned short word 2 0 to 65,535
unsigned long ---- 4 0 to 4,294,967,295
char ---- 1 -128 to 127
---- char 1 1 ASCII character
int integer 2 -32,768 to 32,767
short ---- 2 -32,768 to 32,767
---- shortint 1 -128 to 127
long longint 4 -2,147,483,647 to 2,147,483,647
float single 4 3.4E-38 TO 3.4E+38
double double 8 1.7E-308 TO 1.7E+308
long double extended 10 3.4E-4932 TO 3.4E+4932
---- comp 8 1.7E-308 TO 1.7E+308 (for 8087/80287)
---- real (for backwards compatibility only -- use double)
void pointer 8 n/a -- an untyped pointer
---- boolean 1 True or False
(C++ may soon have a boolean type)
String ---- a C++ standard object
---- string an array of up to 255 ASCII characters
---- PChar pointer to a null-terminated string
=================================
Keywords
C++ has 59 keywords, and OP has 60 keywords. This does not include the many vendor
specific extensions to the C++ language or preprocessor directives. OP is specific to
Borland, so the notion of cross-platform compatibility is not applicable. Most C++
implementations are ANSI compatible.
C++ is case-sensitive; OP is not.
C++
asm auto break case catch cdecl char class const const_cast continue
default delete do double dynamic_cast else enum extern far float for
friend goto huge if inline interrupt int near new operator pascal private
protected public register reinterpret_cast return short signed sizeof
static static_cast struct switch template this throw try typedef typeid
union unsigned virtual void volatile wchar_t while
OP
and as asm array begin case class const constructor destructor div do
downto else end except exports file finally for function goto if implementation
in inherited inline initialization interface is label library mod nil not
object of or packed procedure program property raise record repeat set shl
shr string then to try type unit until uses var while with xor
=================================
Statement terminators
C++
Most statements are terminated with a semi-colon ;
There are a couple exceptions...
#include
#define MAXNAMELENGTH 35 // also doesn't end with a semi-colon ;
OP
All statements end with a semi-colon ;
=================================
Declaring variables
C++
Maximum identifier length is 32 characters -- can be longer, but only the first 32
are recognized.
Variables can be declared anywhere in code, and variables do not all have to be
declared at the same location (but must be declared before use, of course)
// ... anywhere in code ...
char sName[10];
int iCount, iLoop, iValToReach;
double dMaxLoan, dTotal;
float fMaxRate = 123.875; // notice you can assign a value when declaring
OP
Maximum identifier length is 63 characters -- can be longer, but only the first 63
are recognized.
Variables must be declared in "var" code block at the start of a procedure or
function or in an object definition before procedure and function declarations. Values
cannot be assigned in the var code block.
function PayBack_d(var dAmount: double): double;
var
iCount, iLoop, iValToReach: integer;
dMaxLoan, dTotal, dMaxRate: double;
begin
dMaxRate := 123.875;
{...}
=================================
Strings
C++
There is a string object in the standard C++ library now. Unfortunately, it is
not compatible with the more commonly used null-terminated character array. (The
null character is "/0".) Because most strings are character arrays, they can be an
unlimited length. This is how character arrays are declared...
char sName[26]; // 25 chars plus null
char psDemo[] = "Hello, there!";
char * psDemo = new char[26];
The two things you will do most with a string/character array are copy into
and concatenate (add to) the string. The only thing you must make sure of is that the
array you are copying/concatenating to is large enough to hold the final result. Here
is an example of character arrays, copying, and concatenating...
class Ticker
{
...
public: // notice colon
char sBuf[10],
sLatestTrade[TRADELENGTHBUF],
saTradeRecords[50] [TRADELENGTHBUF];
...
void OptnFormat2_v(unsigned long & ulQuantity,
CompanyC * poC,
int & iSeries);
...
};
...
void TickerC::OptnFormat2_v(unsigned long & ulQuantity,
CompanyC * poC,
int & iSeries)
{
ultoa(ulQuantity, sBuf, 10);
strcpy(sLatestTrade, sBuf);
AddMosToString_v(sLatestTrade,
poC->oSOS.oSeries[iSeries].oExpDate.enMonth);
itoa(poC->oSOS.oSeries[iSeries].oExpDate.iDay, sBuf, 10);
strcat(sLatestTrade, sBuf);
strcat(sLatestTrade, poC->oS.sTicker);
double dStrike = poC->oSOS.oSeries[iSeries].dStrike;
gcvt(dStrike, 3, sBuf);
strcat(sLatestTrade, sBuf);
if(poC->oSOS.oSeries[iSeries].enCallPut == Call)
strcat(sLatestTrade, "Calls");
else strcat(sLatestTrade, "Puts");
} // end TickerC::OptnFormat2_v
OP
String is a valid data type that can hold up to 255 chars (unlimited in Delphi 2).
Strings in OP are 1 byte bigger than the size you declare. That's because strings are
still character arrays in OP, and the first element, position [0], contains the size
of the array. Also, you delimit strings between SINGLE quotation marks...
var
sMyName: string[25]; {only 25 chars in this string}
sYourName: string; {up to 255 because size not specified}
begin
sMyName := 'Paul Fulford'; {notice SINGLE quotation marks}
In OP, you can copy to a string variable with the assignment operator (:=), and
concatenate with the + plus sign. Here is the same example shown above using OP...
TickerC = class
...
public {notice no colon}
sLatestTrade: string[TRADELENGTHBUF];
saTradeRecords: TStringList;
...
procedure TickerC.OptnFormat2(var lQuantity: longint;
poC: CompanyC;
var iSeries: integer);
...
end;
...
procedure TickerC.OptnFormat2(var lQuantity: longint;
poC: CompanyC;
var iSeries: integer);
begin
sLatestTrade := IntToStr(lQuantity);
AddMosToString(sLatestTrade,
poC.oSOS.oSeries[iSeries].oExpDate.enMonth);
sLatestTrade := sLatestTrade +
IntToStr(poC.oSOS.oSeries[iSeries].oExpDate.iDay) +
poC.oS.sTicker +
FloatToStr(poC.oSOS.oSeries[iSeries].dStrike);
if poC.oSOS.oSeries[iSeries].enCallPut = Call then
sLatestTrade := sLatestTrade + 'Calls'
else sLatestTrade := sLatestTrade + 'Puts';
end; {OptnFormat2}
If you take a close look (you will, won't you?), you'll see that the OP way is
much easier. The difference between C++ character arrays and OP strings is in the use
of element [0]. In OP, it contains the string size. In C++ the array is read until
the "/0" null character is reached, and element [0] contains the first character you
want.
=================================
Arrays
Arrays are ordered sequences of one data type (can include objects, too). The
methods for declaring an array in C++ and OP are different, but individual array
elements are accessed the same way.
C++
Arrays are "zero-based" -- the first element is element [0], the second element is
element [1], third is [2], etc. This is always confusing for beginners.
// declare arrays...
DateC aoCANHolidays[SIZE_HOLIDAYARRAY];
double dAverageLast31Days[31];
// use array...
for(int i = 30, j = 29; i > 0; i--,j--)
dAverageLast31Days[i] = dAverageLast31Days[j];
OP
Arrays start at element [1] ... well, not always. In Delphi, you will find some
components and objects that are still zero-based as in C++. (TList.Items is one of
those arrays that is zero-based. It is discussed in the section on containers.) We
just have to pay attention to the documentation. Any array that you declare, though,
will be one-based.
var
aoCANHolidays: array[1..SIZE_HOLIDAYARRAY] of DateC;
dAverageLast31Days: array[1..31] of double;
i,j: integer;
begin
j := 30;
for i := 31 downto 2 do
begin
dAverageLast31Days[i] = dAverageLast31Days[j];
Dec(j); { or j := j-1; }
end;
Both languages support multi-dimensional arrays...
C++
double dMatrix[50] [20];
OP
var
dMatrix: array[1..50, 1..20] of double;
=================================
Assigning and comparing values
C++
Compare values using == double equal sign.
Assign values using = single equal sign.
ie:
if (dMaxRate == 15.75)
{ ...
dMaxRate = 12.5;
...
}
OP
Compare values using = single equal sign.
Assign values using := colon equal sign.
ie:
if dMaxRate = 15.75 then
begin
...
dMaxRate := 12.5;
...
end;
An exception to the rule in OP is assigning values to a constant and declaring
classes. Use = single equal sign.
=================================
Declaring constants
Constants are values that do not change (no kidding). An identifier can be
declared a constant in both C++ and OP. Both C++ and OP constants must be assigned a
value when declared.
C++
Declare constants by preceding the data type with the keyword "const".
ie:
const int iMax = 2000;
const double dMaxValue = 1234.75;
In C++, you can also declare constants with the #define preprocessor directive
like this...
#define nMAXAMOUNT 1000
... but this practice is growing obsolete because type checking cannot be
performed (the compiler doesn't know if it's a double or an int in this example).
OP
Constants, like variables, must be declared in a "const" code block at the start
of a procedure or function definition or in an object declaration.
ie:
function PayBack_d(var dAmount: double): double;
const
iMAX = 2000; {notice value assigned with single equal sign this time}
dMAXVALUE = 1234.75;
var
iCount, iLoop, iValToReach: integer;
dMaxLoan, dTotal, dMaxRate: double;
begin
dMaxRate := dMAXVALUE;
{...}
=================================
Functions and procedures
Code blocks that perform a specific task in C++ are called "functions" regardless
of whether or not they return a value. In OP, functions must return a value, and
procedures do not return a value. In C++, all functions must be prototyped/declared
before being defined (so the compiler can compare both to ensure consistancy).
In OP, a function or procedure definition can omit (but does not have to) the
parameter list and omit the return type. My preference is to exactly copy the declaration
statement to the definition block so I don't have to scroll back and forth if I forget
the parameters or return type.
C++ function declarations and definitions require braces() regardless of whether or
not there are any parameters. OP functions and procedures do not need braces() when
declaring or defining them if there are no parameters to pass to the function or procedure.
C++
ie:
double IntSinceLastAddDate_d(double &dAvailCash); // prototype
void SetNewIntRate(); // no parameters or return value
...
double LoanC::IntSinceLastAddDate_d(double &dAvailCash)
{
double dSomething;
...
return dSomething;
}
void LoanC::SetNewIntRate()
{ ... }
OP
Each function and procedure must be identified as such by including the keyword
"function" or "procedure" at the start of each.
ie:
function IntSinceLastAddDate_d(var dAvailCash: double): double;
procedure SetNewIntRate; {no parameters or return value}
...
function LoanC.IntSinceLastAddDate_d(var dAvailCash: double): double;
var
dSomething: double;
begin
...
result := dSomething;
{the global variable "result" is assigned the return value!}
end;
procedure LoanC.SetNewIntRate;
begin
...
end;
Both C++ and OP can pass parameters by value or by reference or pass constants...
C++ pass by value ... double IntSinceLastAddDate_d(double dAvailCash);
OP pass by value ... function IntSinceLastAddDate_d(dAvailCash: double): double;
C++ pass by reference ... double IntSinceLastAddDate_d(double &dAvailCash);
OP pass by reference ... function IntSinceLastAddDate_d(var dAvailCash: double): double;
C++ pass constant ... double IntSinceLastAddDate_d(const double dAvailCash);
OP pass constant ... function IntSinceLastAddDate_d(const dAvailCash: double): double;
Both C++ and OP allow function/procedure overloading as long as their signatures
are different.
=================================
The with ... do statement in OP
Generally, C++ is more compact than OP. But, C++ does not have a with ... do statement.
That's unfortunate because it really is a great feature of OP. Consequently, even with
copying and pasting, C++ code can be more verbose in spots.
In C++, when you have to access data members, you end up doing something like this...
poC.oStock.aoTradesLast130Days[0].lVol = 0;
poC.oStock.aoTradesLast130Days[0].dHigh = 0;
poC.oStock.aoTradesLast130Days[0].dLow = 0;
poC.oStock.aoTradesLast130Days[0].dClose = 0;
But in OP, you can make it much more readable by doing this...
with poC.oStock.aoTradesLast130Days[0] do
begin
lVol := 0;
dHigh := 0;
dLow := 0;
dClose := 0;
end;
A little easier to read, don't you think?
=================================
Commenting
C++
There are two ways to comment in C++ ...
// anything after a double slash is a comment to the end of that line
/* anything between a slash star and star slash is a comment no matter where
it occurs */
OP
In Delphi V1, comments are either enclose between {braces} or (*parenthesis
star combinations*). This will require careful attention when converting because braces
in C++ are used to delimit code blocks.
{ this is a comment in OP }
(* so is this *)
In Delphi V2, you will also be able to use the double slash // just like in C++
now.
=================================
Controlling program flow
There are five controlling structures in both languages, and they are quite
similar. This will take a fair bit of room to review.
~~~~~~~~~~~~~~~~~~~~~~~~~
1) The if ... else conditional construct:
C++
if(
{ ...
}
else if(
{...
}
else
{...
}
OP
if
begin
{single expressions do not have to be enclosed in
braces. Also notice the "then" following the expression.}
...
end
else if
begin
....
end
else
begin
...
end; {only the very last "end" in the sequence terminates with a semi-colon.}
~~~~~~~~~~~~~~~~~~~~~~~~~
2) The switch/case ... conditional construct:
C++
switch(
{
case iX: ... break;
case iY: ... break;
default: ...
}
OP
case
{no "begin" here}
iX:
begin
...
end; {semi-colon after each end;}
iY:
begin
...
end;
else {no colon here}
begin
...
end;
end; {and an "end;" here}
~~~~~~~~~~~~~~~~~~~~~~~~~
3) The for ... loop construct:
C++
for(iCount = 0; iCount <= 10; iCount++)
{
// the increment clause of the for loop, iCount++, can be incremented
// by any amount, not just 1 as shown. You can also decrement the
// the loop counter
...
break; // to escape
continue; // to immediately loop
...
}
OP
for iCount := 1 to 10 do
begin
... {for loops can only increment by 1. To decrement use the 'downto' keyword.}
break; { to escape }
continue; { to immediately loop }
...
end;
There is one difference between the "break" and "continue" implementations in the
languages. In C++, break and continue are keywords -- a part of the language; in OP,
however, they are library procedures. Same usage as far as we're concerned, though.
~~~~~~~~~~~~~~~~~~~~~~~~~
4) The while ... loop construct:
C++
while(
{
// expression is tested at start of loop, so code may never run if
// expression evaluates to false
...
break; // to escape
continue; // to immediately loop
...
}
OP
while
begin
{ expression is tested at start of loop, so code may never run if
expression evaluates to false }
...
break; { to escape }
continue; { to immediately loop }
...
end;
~~~~~~~~~~~~~~~~~~~~~~~~~
5) The do/repeat ... loop construct:
C++
do
{
// expression is tested at end of loop, so code always runs at least once
...
break; // to escape
continue; // to immediately loop
...
}while(
OP
repeat
{ expression is tested at end of loop, so code always runs at least once.
also notice also there is no begin ... end delimeters in this type of loop }
...
break; { to escape }
continue; { to immediately loop }
...
until
=================================
Object oriented structures
Both languages call their object structures "classes". C++ handles multiple
inheritance, but OP only handles single inheritance.
Let's look at the basic syntax for creating classes...
C++
Classes are declared in a header file...
class LoanC // no inheritance shown here
{
private: // private by default
...
protected: // notice colons after keywords
...
public:
...
}; //semi-colon terminated
... the syntax for single inheritance in C++ is ...
class B: A
{...};
... and for multiple inheritance in C++ ...
class D: A, B, C
{...};
Those classes that are inherited in C++ can also be identified as public,
protected, or private. The default is private unless otherwise specified...
class D: public A, public B, private C
{...};
OP
Classes are created in a unit/file's interface "type" code block...
type
LoanC = class(TObject)
{This shows inheritance from TObject. TObject is implicitly inherited --
you don't have to specify it -- but the syntax to inherit from some other
object is the same}
{Notice no "begin" statement. }
{Notice class declared with single equal sign}
private
...
protected {no colon after keywords}
...
public
...
published
...
end;
Those keywords private, protected, public, and published (OP only) specify the
"scope" of each data member and function. Scope tells the compilers what parts of the
program and derived classes (if any) can use the data members and functions declared
in this class. Generally, private data members and functions can only be used by other
functions in this class. Protected data members and functions can be used by this
class and derived classes. Public data members and functions can be used anywhere in
the program. Published data members are Delphi specific -- these are the attibutes of
the components you use to build interfaces.
The default is private in C++ classes, public in OP classes. In OP, identifiers in
the component list after the class header are public.
Every class requires a constructor. A constructor is a class member function
whose job it is to initialize the data members of an object just created in memory.
You can have many constructors in both languages.
In C++, constructors have the same name as the class. You can "overload" (have more
than one) constructors by declaring the constructors with different parameter lists.
ie:
LoanC(); // constructor with no parameters
LoanC(double &dCurrentBal); // one parameter
LoanC(double &dBalOne, double &dBalTwo); // two parameter, etc...
In OP, constructors have different names. You identify the procedure as a
constructor by preceding the constructor declaration with the keyword "constructor".
ie:
constructor MyLoanOne; {no parameters}
constructor MyLoanTwo(var dCurrentBal: double);
constructor MyLoanThree(var dBalOne, dBalTwo: double);
Each language also supports "destructors" which are functions that are called to
free any memory allocated by the constructors and do other chores before the object is
freed from memory.
Destructors in C++, like their constructors, have the same name as their class, and
are identified by preceding the class name with a tilde...
~LoanC(); // C++ destructor
You can have different destructors in OP, but Borland recommends you simply
override the inherited TObject's "Destroy" destructor like this...
destructor Destroy; override; {"override" is a directive}
... and then in the destructor's definition, call TObject's Destroy after doing your
own clean-up...
destructor LoanC.Destroy;
begin
oLoanDate.Free;
...
inherited Destroy; {"inherited" is a keyword}
end;
To create a new object in C++, just declare a variable of that type...
double dAmount = 1515.75;
LoanC oMyLoan(dAmount);
... this actually allocates memory and creates the object in memory. Or, in C++,
using a pointer, do this...
double dAmount = 1515.75;
LoanC * poMyLoan = new LoanC(dAmount);
... which means you'll have to access the new loan class using the poMyLoan->
pointer.
In OP, it's different. Everything is a pointer, so simply declaring an object in a
var block doesn't actually allocate any memory/create the object.
var
dAmount: double;
oMyLoan: LoanC;
begin
{oMyLoan does not yet exist!}
dAmount := 1515.75;
oMyLoan := LoanC.MyLoanTwo(dAmount); {now it does}
In OP, you can also override the default "Create" constructor like this...
type
LoanC = class
...
constructor Create; {overrides TObject's Create}
...
end;
... and call the inherited Create constructor in the LoanC definition...
constructor LoanC.Create;
begin
inherited Create; {calls TObject's Create}
...
end;
In both C++ and OP, you access an object's data members and functions/procedures
using dot notation...
oMyLoan.dPrincipal;
In C++, if you are using a pointer, you would access the object using the indirect
selector operator ->
poMyLoan->dPrincipal;
In C++, there are three operators used to work with objects and other data
types allocated in memory: 1) the & referencing/address-of operator, 2) the *
dereferencing operator, 3) the -> indirect selector operator.
Every object in OP is a pointer (as explained earlier), but we let the compiler
worry about the details. This makes OP much easier, and we can stick to using dot
notation.
=================================
Containers
Containers are special class objects that are used to store data. Think of
containers as database classes and/or classes that are intended to store information
that simple arrays cannot.
Containers in C++ are implemented through templates. You tell the template what
kind of object the container will be storing. In addition to the container itself, you
also (are supposed to) implement an iterator template based on the type of object in
the container. You also have to overload the == comparison operator, write a default
constructor, and if you want the container sorted then overload the < less than
operator to facilitate sorting. Sounds easy, huh?!
Of course, each vendor has their own library of container templates, so until
recently there was no industry-wide standard. That has changed recently with the
distribution of the "STL" Standard Template Library which many vendors are finally
releasing with their compilers.
The STL was originally developed at Hewlett-Packard, but is now a set by the
ANSI/ISO C++ Standards Committee. The STL was originally accepted as part of the C++
Standard Library in July, 1994, but its taken a while to get out into the various
compilers.
Nonetheless, the syntax for implementing a template-based container is downright
bizarre. Unlike the OP technique (which you will see), C++ templates are still overly
complex.
I have not used the STL, but I have seen enough to know it's still complex. Here
is an example of a BIDS container implementation...
typedef TISetAsVector
typedef TISetAsVectorIterator
...
int OwlMain(int, char*[])
{...
tdCompanySet oCompColl;
...
}
...
void MainForm::AddNewCompany()
{
CompanyC * poC = new CompanyC;
oCompColl.Add(new CompanyC(poC));
...
// now iterate
tdCSetIter oCIter(oCompColl);
while(oCIter)
{
poC = oCIter++;
// now use poC
...
}
}
Containers in OP are easily implemented. Simply use a TList object to "contain" a
list of objects like this...
TMainForm = class(TForm)
...
public
oCompColl: TList;
...
end;
...
procedure TMainForm.AddNewCompany;
var
poC: CompanyC;
iCount: integer;
begin
poC := CompanyC.Create;
...
oCompColl.Add(poC);
...
{now iterate}
for iCount := 0 to oCompColl.Count - 1 do
begin
poC := oCompColl.Items[iCount];
{now use poC}
...
end;
Since all objects in OP have TObject as a base class, it's easy for Borland to
implement a generic container like TList. The idea is both good and bad. It does limit
you to containing objects that are inherited from TObject -- that's not much good if
you want a bag of doubles. (A "bag" is a special type of vector container.) In this
case, you'd have to create some sort of doubles class based on TObject, which is
nonsense. (Just create an array of doubles and iterate over the array some custom way.)
Furthermore, I can hear the howls already: "But what about sets, and hash tables,
binary trees, linked lists, and deques!" Yes, yes, a TObject/TList is pretty simple.
And it isn't sorted (but some components in Delphi will sort!). Sure, there will be times
when it won't do, but they worked for me in Komodo Market, and with a lot less pain and
frustration. Yes, I had to write my own sorting algorithms. Yes, yes, yes, leave me alone.
The TList syntax is so much easier, that C++ containers are unnecessary for all
but special circumstances. I know, I've used both.
=================================
Exception handling
An exception is a runtime error. Error information is passed to your program in an
error object. There are different types of error objects depending on the type of
exception (makes sense). However, some programs have so many runtime errors, they are
the rule, not the exception (har, har ... hem).
C++
try
{
...
}
catch(
{
...
}
catch(...) // dot dot dot catches any type of exception
{
...
}
You can also cause an exception in C++ using the "throw" keyword...
throw
OP
try
{no begin ... end; statements}
...
finally
{release/clean up memory statements -- error handling, not really
'exception handling'. This code always executes regardless of whether
or not an error occurs. C++ has no equivalent.}
...
end;
To capture specific runtime errors in OP, the syntax is...
try
...
except
on
{this code only executes if there is an exception.}
end;
You can also cause an exception in OP using the "raise" keyword...
raise
=================================
Streaming
In C++, you override the << (put to/insertion) and >> (get from/extraction)
operators passing them a stream object. It's easier in OP, but Borland didn't have
any examples in their documentation, so I'll show you how to stream out to a binary
file (not a text file).
C++
class CompanyC
{
...
friend ofstream & operator <<(ofstream &oS, CompanyC * poC);
...
};
...
friend ofstream & operator <<(ofstream &oS, CompanyC * poC)
{
oS << poC->dVersion
<< poC->dLastDivPerShare
<< poC->enIndustry
...
<< poC->sName;
return oS;
}
... or, in this example, if the CompanyC object had no internal pointer objects,
you could shorten the whole thing to this...
friend ofstream & operator <<(ofstream &oS, CompanyC * poC)
{
oS.write( (char*) & (*poC), sizeof(*poC));
return oS;
}
...easy, huh?! You would do the same with the >> operator.
Now open and use a file like this...
// open a stream
ofstream ofS("gamedata.gam");
if(!ofS)
return;
...
// save info to file
tdCSetIter oCIter(oCompColl); // see container section
while(oCIter)
{
poC = oCIter;
ofS << poC;
}
ofS.close();
OP
type
CompanyC = class
public
procedure Read(var f:file);
procedure Write(var f:file);
...
end;
...
procedure CompanyC.Write(var f:file);
begin
BlockWrite(f, dVersion, sizeof(dVersion));
BlockWrite(f, dLastDivPerShare, sizeof(dLastDivPerShare));
BlockWrite(f, enIndustry, sizeof(enIndustry));
...
BlockWrite(f, sName, sizeof(sName));
end;
Now open and use a file like this...
procedure TMainForm.FileSaveItemClick(Sender: TObject);
var
oGameFile: file;
iCount: integer;
poC: CompanyC;
sNameFile: string[13];
begin
...
sNameFile := 'gamedata.gam';
AssignFile(oGameFile, sFileName);
Rewrite(oGameFile, 1); {the 1 means 1 byte at a time}
...
for iCount := 0 to oCompColl.Count - 1 do
begin
poC := oCompColl.Items[iCount];
poC.Write(oGameFile);
end;
CloseFile(oGameFile);
end;
=================================
Arrangement of project files
In C++, you typically put all your data type defines, constants, and class declarations
in a header file with the .h extention. Program implementation and class function
definitions are done in .cpp files. A .cpp file uses the #include directive to find a
.h file.
In Delphi's implementation of OP, one file is used for both declarations and
implementations. Files are called "units", and have a .pas extention. Each unit
contains an "interface" section where data types, constants, and global variables (to
the unit) are declared, and an "implementation" section where class functions are
defined.
For one unit in OP to use classes found in another unit, the program adds a "uses"
clause to either the interface or the implementation section (sometimes to both).
There is nothing in C++ forcing you to use the .h and .cpp method -- that's just
common. You could put your declarations at the start of any .cpp file and all would be
fine unless you encounter a "circular" reference where one file #include(s) another file
which #include(s) the original file. This is why, in OP, you might have a "uses"
clause referring to some files in the interface and another "uses" clause referring to
other files in the implementation section.
In OP, each form has a .pas file for your code and a binary .dfm file that
contains form details. (.dfm := 'Delphi form' -- get it?!)
=================================
How to make the conversion
You will have more difficulty converting to Delphi if your interface code is
mixed in with your non-interface code. In that case, you may just want to create the
Delphi interface forms as shells, and then copy in your C++ non-interface code to
appropriate event handlers, and then follow this advice.
Don't do this on the originals of your C++ files! Use copies!
Open your C++ files in Delphi, and use the Delphi editor to do the following...
1) Do a global replace of all C++ {...} code blocks delimiters with OP begin...end; code
block keywords. This is easier if your C++ syntax was like this...
double CompanyC::NewAnnualReport_v(Economy & oE)
{ ... // delimiter starts on a new line
}
...versus this...
double CompanyC::NewAnnualReport_v(Economy & oE){ // delimiter at end of line
...
}
2) a) Globally replace all C++ || or operators with OP "or" keyword.
b) Globally replace all C++ && and operators with OP "and" keyword.
c) Globally replace all C++ = assignment operators with OP := operator. Make sure
you specify "Whole words only" because you don't want to replace C++ == comparison
operators with two OP :=:= operators. You shouldn't have any single = operators after this.
Then replace C++ == with OP = operator.
d) Globally replace all C++ /* */ comment delimiters with OP { } comment
delimiters.
e) You can do a global replace of all C++ // comment delimiters with the OP {
start comment delimiter, but you'll have to manually add the closing OP } comment
delimiter yourself. If you're already working in Delphi 2.0, you can leave
your // comments.
f) Globally replace double quotation "..." marks used in C++ with single '...'
quotation marks in OP.
3) At the end of every "if" C++ statement, you'll have to add the OP keyword
"then". Also, multiple conditional expressions in C++ can be separated by the ||
operator or by the && and operator without braces surrounding each condition. In OP,
you have to surround each conditional expression with braces. In C++, one set of
braces surround all conditions.
C++
if(oE.enDirection == Up &&
oE.uNumMosUpYr >= oE.uNumMosDownYr)
{
...
}
OP
if (oE.enDirection = Up) and
(oE.iNumMosUpYr >= oE.iNumMosDownYr) then
begin
...
end;
4) Go through your code making other controlling structure changes as required.
5) C++ overloaded operators will have to be re-written as class functions (or
procedures, depending on how you want to implement the code) in OP.
6) The C++ scope resolution operator :: can be replaced with the . dot operator at
every function or procedure definition. Function and procedure definitions in OP also end
with a ; semi-colon.
procedure TForm1.Memo1Change(Sender: TObject); {see semi-colon here}
begin
...
end;
7) Replace all C++ increment ++ and decrement -- operators with the OP inc(iX) and
dec(iX) procedures if iX is of an integer type. Otherwise, you'll have to do the more
verbose iX := iX + 1.875;
One other thing. The location of the ++ and -- operators has an effect on your
code in C++. Those operators can be pre-fixed or post-fixed to increment or decrement
the variable either before or after the variable is used. So watch where you put your
OP replacement code.
8) You have to create a "var" code block in OP, and move all your variable
declarations to the var block. Replace all the C++ data types with the appropriate OP
data types. And since you can't initialize any variables in a var block, you'll have to
re-initialize them somewhere in the begin ... end; code block (before being used, of
course).
9) Add the OP keywords "function" or "procedure" before those C++ functions that
either return a value or don't return any value.
10) All those pointers in C++ have got to go. Replace -> with the . dot operator
for class member access. Furthermore, simply declaring an object variable in OP does
not allocate memory for it. While this...
DateC oBirthDate;
... would work in C++, you have to do this...
var
oBirthDate: DateC;
begin
oBirthDate := DateC.Create;
... in OP because every object identifier is a pointer. But in OP, we use dot
notation versus the C++ -> indirect member selector operator.
11) All the C++ strcpy(...) and strcat(...) functions can be replaced with OP
assignment (:=) operator and concatenate (plus sign +) operator respectively. There
are other functions that you'll have to convert, too, but overall it's not too hard.
12) What to do about multiple inheritance. I suggest, for example, having one
class inherit from the base, then the third class inherit from the second, etc. So
class B would inherit from class A, and then class C would inherit from class B. If
you have many classes being inherited into another class, it would seem to me that
the design is lacking, and you should re-think things.
=================================
Summary
C++ is more of a shorthand language, and OP is more english-like. Therefore, C++ is
more compact, but it is harder to read. You'll have to do more typing in OP (even
considering the with ... do statement).
C++ creates copy constructors automatically and allows multiple inheritance. C++
allows operator overloading and comes with ++ increment and -- decrement operators.
But all that doesn't matter because the GUI form builder in OP will save you so
much time that it puts C++ out of the picture. C++ development tools have all the event
handlers built in, and it's not that hard to add code to them, but you can't see your
interface as you build it -- and that's the key. In C++, whenever you want to adjust
your interface objects to new positions, it's a nightmare. Not so in Delphi.
Furthermore, trying to read someone else's C++ code is almost impossible. Every
C++ programmer comes up with their own shortcuts to prove their programming prowess.
Someone else's OP code is much easier to read.
Another point is the compile and link time between compilers. The difference in
compile and link times between C++ and OP is astounding. I can compile and test my OP
programs in a fraction of the time my C++ compiler takes. This makes for true RAD
development. (May have something to do with the way parameters are parsed.)
So why not use another development tool such as Visual Basic? VB is not object-
oriented, doesn't compile to a standalone executable, and its programs run slower.
=================================
Stuff not covered
1) C++ copy constructors vs OP Assign(...) procedure
2) virtual functions to allow polymorphic behavior
3) pointers, typecasting, and how to allocate global memory (heap memory)
4) C++ structs vs OP record objects
5) using null-terminated strings in OP. Yes, you still need them sometimes. The
Windows APIs all require null-terminated strings, so Delphi programmers should become
familiar with them. Delphi 2 has an interesting (undocumented) way of handling its new
strings (hint: they are null-terminated, but the compiler handles the details).
6) C++ bitwise operators vs OP subranges, enumerations, and sets. I refer you to
John O'Connell's article in the November, 1995 Delphi Informant magazine. John covers
Pascal subranges, enumerations, and sets.
7) text streaming: input and output in text files. There are plenty of examples
supplied by C++ vendors and by Borland in the Delphi documentation.
8) C++ escape sequences and their OP #
return is represented by "/n". OP allows you to specify such non-printing characters
by concatenating the ASCII value into a string using the pound sign and the ASCII
value of the character you need. The carriage return in OP is #13. The bell is #7.
There are others. (256 others, from 0 to 255, if you include the printing characters!)
9) OP allows nested local functions/procedures declared in a block after the start
of a definition and the 'begin' keyword of the function/procedure itself. This is kind
of cool.
10) limitations to multi-dimemsional arrays.
Whew.
============END==============
Yes, the program displaying this article was written in Delphi. It took about 15
minutes.
This article is copyrighted (c) 1996, 1997 by Paul Fulford. It may be distributed
freely as long as I'm given credit as the author, and as long as you don't charge for
it. Please let me know what I've missed and/or any mistakes.
Komodo Software specializes in FoxPro, Paradox, and Delphi (and, okay, C++, but I'm
trying to get away from that). I can be reached at...
Komodo Software
Box 108, 141-6200 McKay Avenue
Burnaby, B.C.
Canada, V5H 4M9