| C++Builder and Delphi have a unique relationship that enables
programmers to easily share code between the two environments. Almost anything
you write in Delphi can easily be used in C++Builder, and some of the code you
write in C++Builder can be used in Delphi. As a result, programmers can work in
either C++ or Delphi, depending on their skill level or inclination. Despite
their similarity, each has capabilities:
- Delphi's ease of use enables a wide range of programmers to quickly
produce high performance, robust components, objects, and applications.
- C++Builder, on the other hand, allows the best programmers to write code
in a language known for its flexibility, subtlety, and wide acceptance.
Because you can share code between the two environments, programmers can use
the tools that best suit their current needs. Then later on they can swap the
code back and forth between the two environments.
This chapter has somewhat unusual origins, in that I created it originally as
a white paper for use on Borland's Web sites. I have decided to include it in
this book without extensive revisions because it covers an important topic in a
fairly short space. Readers with good ears will hear a rather different tone in
this chapter than you find in the other sections of this book.
Because of its origins, this chapter does not contain as many sample programs
as most of the chapters in this book. However, if you look in the Chap06
directory on the C++Builder Unleashed CD-ROM, you will find a few programs
illustrating the main points of this chapter.
This chapter is divided into two main sections. The first part contains a
summary of the techniques for sharing code between the two environments. The
second, and longer, section of the chapter contains a more technical analysis of
the methods for sharing code between C++Builder and Delphi.
Benefits of Sharing Code Between C++Builder and Delphi
This section lists some of the key benefits gained when you share code
between Delphi and C++Builder. These items are listed in no particular order:
- Programmers can work in either C++ or Delphi. You don't have to worry that
code written with one tool will not be accessible from inside the other tool.
Instead, you can concentrate on finding the fastest way to create usable
modules, and then link them in to either C++Builder or Delphi, depending on
the needs of your engineers. Of course, the route from Delphi to C++Builder is
much more open than the route from C++Builder to Delphi, but it is possible to
move in either direction.
- Sharing code between Delphi and C++Builder also promotes code reuse. You
can write an object once for a Delphi project, and then reuse it, unchanged,
in a C++ project. There is no performance or size penalty associated with
writing code in Delphi rather than C++. Anything you might have heard about
C++ being significantly faster than Object Pascal is simply untrue.
- Delphi is extremely easy to use. It offers programmers a simple,
easy-to-understand syntax. Companies can allow inexperienced programmers to
work in Delphi without fear that their code cannot also be used in C++
projects. You can therefore have some of your team working full-time in C++,
and the rest of the team working full-time with the much easier to use Delphi
language. The efforts of both programmers can then be combined into a single,
high-performance executable.
- Because Delphi is so easy to use, it is often the language of choice
during projects that have tight deadlines. Even your best programmers might
prefer to use Delphi when they are rushed, or when they want to create
high-performance programs as easily as possible. Any modules or objects
created on these types of projects can then later be linked in to C++
projects. This means that there is no penalty associated with using Delphi,
even if you are primarily a C++ shop.
Technical Highlights
This section of the chapter contains an overview of the key points you need
to know about sharing code between C++Builder and Delphi. It consists of two
subsections called Overview: Using Delphi Code in C++Builder
Overview: Using C++ Code in Delphi
When you are through reading these sections, you will have a general feeling
for how, and to what degree, you can share code between Delphi and C++Builder.
In subsequent sections, I will review most of these points in more detail.
Before beginning, you should note that Delphi uses Object Pascal, which is
widely taught in many schools. C++Builder uses C++, which is probably the most
widely used language in corporate America. Most programmers will know one, if
not both, of these languages.
Overview: Using Delphi Code in C++Builder
C++Builder does not care whether the units you add to your project are
written in C++ or in Delphi. It will compile either type of unit as if it were
native to C++Builder.
As a general rule, any Delphi 2.01 unit you create that compiles in Delphi
should compile unchanged in C++Builder. There are a few Delphi syntactical
elements that will not be accepted by C++Builder. However, these are minor
exceptions, and you will generally find that all your Delphi 2.01 or Delphi 2.0
code will compile unchanged in C++Builder. After all, the entire VCL is written
in Object Pascal, and it compiles fine under BCB. The VCL consists of many
thousands of lines of code, and contains almost any imaginable Object Pascal
construct.
In particular, the following types of code will compile in C++Builder:
- 1. Delphi forms
2. Delphi units containing objects
3. Delphi units containing procedures, functions, constants,
structures, arrays, and so on
4. Delphi components
You usually do not have to change your Delphi units at all to link them in to
C++Builder. There is no need to change the code, to create header files, or to
massage your code in any way. You can mix the types of code listed previously in
any way you like. For instance, you can link in to C++Builder one Delphi unit
that contains the following:
- A form
- An object
- Some functions
- A component
The unit can contain any combination of the preceding types, or of any other
valid Object Pascal types. These types can appear in any combination. For
instance, a Pascal unit can contain only an object, or it can contain both an
object and a form. You can link in as many Pascal units as you like, with each
containing any combination of the above objects. There is no limit on the number
of Delphi units you can add to a C++Builder project.
To link Delphi code into C++Builder you simply choose Add to Project from the
menus and then browse for files that end in PAS. Once you have added one or more
Delphi units to your project, they will compile and link seamlessly with the
rest of your C++ code. You may edit only the source code of the units while they
are open inside of C++Builder. You cannot edit the forms. You can, however,
create a descendant of a Pascal form and then edit that descendant.
Besides the simple techniques outlined previously, advanced programmers can
also link Delphi code into C++ by using one of the following techniques:
- 1. Delphi dynamic link libraries (DLLs) can be easily added to your
C++ projects.
2. You can use COM and OLE to add Delphi 2.0 or Delphi 3.0 objects to
C++Builder. In particular, you can add Delphi objects to C++Builder through
OLE Automation.
The basic rules of COM, including dual interfaces.
Creating Delphi ActiveX controls that you use in C++Builder.
3. Delphi and C++Builder share the same Virtual Method Tables (VMTs).
This means you can create an object in Delphi and use it in C++Builder through
the technique outlined in the section called "Mapping Virtual Method Tables."
This technique is very similar to what happens when you create a dual
interface in OLE Automation.
Overview: Using C++ Code in Delphi
C++ supports some syntactical elements such as multiple inheritance and
function overloading that make it difficult to directly link all C++Builder code
into Delphi. However, you can still use C++ code in Delphi projects through the
following techniques:
- 1. COM and OLE. Delphi fully supports both OLE Automation and dual
interfaces.
2. DLLs.
3. Direct linking of C++Builder units containing functions. (Note that
Delphi supports OBJ files.)
The most convenient of the preceding techniques is the first, especially if
your C++ COM objects support type libraries. Delphi 97 has full support for type
libraries, and you will be able to generate Delphi units directly from a type
library. This means that you can link C++ COM objects that support type
libraries directly into Delphi without having to write any extra code. All the
necessary files for linking in C++ COM objects that support type libraries will
be generated automatically by Delphi.
DLLs are a powerful means of sharing code between Delphi and C++Builder.
Virtually any C++Builder function that you create can be placed in a DLL and
called directly from Delphi. Delphi supports all the common calling conventions
such as CDECL, PASCAL, and STDCALL. You can access an object written in C++ from
inside Delphi simply by reversing the technique described in the section titled
"Mapping Virtual Method Tables."
It is possible, under certain circumstances, to link C++ OBJ files directly
into Delphi. This is a powerful technique that can be very useful in certain
circumstances. However, there are limitations on what you can do with this
technology, and it should not be viewed as a primary means of porting code
between the two environments.
Summary of Techniques for Sharing Code
Before closing this section of the chapter, it might be helpful to review
some of the key points mentioned so far.
C++Builder has virtually no limitations on its capability to use Delphi 2.01
code. You can simply link your Pascal units directly into C++Builder without
modifying them in any way. They will then compile into the same small, tight,
high-performance machine code you would expect if you wrote them in C++.
If you use both C++Builder and Delphi in a single project, you can promote
code reuse while simultaneously leveraging the skills of all your programmers.
Your best engineers can work in C++, where they can take advantage of that
language's flexibility. Inexperienced programmers, those who prefer Object
Pascal, and programmers who are rushed, can work in Delphi. With this division
of labor, all the programmers on your team will be productive immediately.
The key point is that the technology outlined in this chapter enables your
programmers to work in either C++ or Delphi without fear that the code will not
be available for use in all your C++ projects.
A More Technical Analysis
You have now completed the overview of the techniques for sharing code
between C++Builder and Delphi. For most programmers, this is where the chapter
starts to get interesting. The subject matter in the following sections starts
out with the simplest techniques, and becomes increasingly complex as the
chapter nears its conclusion.
Ground Rules for Linking Delphi Code into C++Builder
You have already learned that you can link Delphi code directly in to
C++Builder projects. In this section, I explain how this is possible, and put to
rest any fears you might have about this kind of project.
First, I will lay down three key ground rules:
- 1. You cannot mix C++ and Delphi code in the same unit. You have to
create separate units (modules) for each type of code.
2. It's best if you have the Pascal source to the unit you want to use.
You cannot link a Pascal binary file (DCU) in to a C++Builder project. You
can, however, link in BCB OBJ files made from Pascal source. In other words,
you must have either the Pascal source for the unit or an OBJ made in BCB. You
can't use a DCU file. Third parties that don't want to distribute source to
their tools can simply recompile once in BCB and then distribute the resultant
binary OBJ files.
3. You cannot edit the visual part of a Pascal form while it is open in
C++Builder. You can, however, edit the source. If you create a descendant of
the Pascal form, you can edit the descendant object with the visual tools.
Furthermore, you should note that a Pascal unit cannot usually be the project
file for a C++Builder application. However, if one small module is written in
C++, and everything else in the project is written in Object Pascal, the project
is still, by definition, a C++Builder project, even though the code is 98
percent written in Object Pascal. The Entities program from Chapter 28, "Game
Programming," is an example of this kind of project.
Here is a detailed description of the simple steps needed to link a Delphi
unit in to a C++Builder project:
- 1. First, choose the Project | Add to Project menu option.
2. When the Add to project dialog appears, pull down the Files of type
combo box at the bottom of the dialog. You will then be able to select files
of type CPP, PAS, C, RES, OBJ, or LIB, as shown in Figure 6.1. You should
select PAS.
3. Browse for the Pascal source file you want and add it to your
project. After you have selected the file you want to use, you can click OK to
add it to your project and proceed exactly as you would if this were a C++
file.
FIGURE 6.1.
Selecting a Pascal file to add to your C++ project.
The header file for your unit will be generated automatically. You don't have to
do anything to the unit to use it in a C++Builder project. An example of this
type of project is found on the CD that accompanies this book, in the Chap06
directory, in a project called Test1.
Linking a Delphi Component in to a C++Builder Project
If you want to use a Delphi component in a C++ project, you would normally
want to first add it to the Component Palette. You do this through the same
basic technique you would use to add a C++Builder component written in C++. In
particular
- 1. Choose Component | Install from the menu.
2. Select the Add button from the Install Components dialog, as shown
in Figure 6.2.
3. Use the Add Module dialog to enter or browse for the name of the PAS
file you want to use--that is, for the PAS file that contains the registration
procedure for your Delphi component.
4. After selecting the module, click the OK button in the Install
Components dialog. C++Builder will then recompile CMPLIB32.DLL,
thereby adding your component to the Component Palette.
FIGURE 6.2.
Adding a Pascal component to a C++Builder project.
Adding a Pascal Component to a C++Builder Project
After you have integrated a Delphi component into C++Builder, you can then
treat it exactly as if it were a native C++Builder component. That is, you can
drop it onto a form and manipulate its methods with the object inspector. If you
need to reference any of the components methods, properties, or events in your
C++ code, you should use C++ syntax. The details of this syntax can be surmised
by viewing the header file created when you added the component to the Component
Palette.
A sample Delphi component called TSmiley is available in the
Utils subdirectory. This component was written by the great Nick Hodges. It
is not only one of the first components ever written in Delphi, but it is also
the world's most famous and most highly acclaimed Truly Useless Component.
TSmiley is a mission-critical component designed for use in distributed
n-tier applications created by Fortune 500 companies. All its functionality can
be accessed through the Mood property, which can be set to various
values such as smHappy, smSad, smIndifferent,
smShades, and smTongue.
Some Theory on Linking Object Pascal and C++Builder
Code
Delphi is an Object Pascal-based language, while C++Builder is obviously a
C++-based language. How is it possible to link Pascal directly into a C++
project?
A good way to begin your exploration of this issue is to understand that
Delphi 2.0, Delphi 3.0, BCB, and Borland C++ 5.X use the same compiler. The
difference is simply in how the language is parsed. Delphi parses Object Pascal,
tokenizes it, and then passes it on to the compiler. Borland C++ 5.X and BCB
parse C++, tokenize it, and then pass it on to the compiler. Underneath the
hood, Delphi 2.0, C++Builder, and Borland C++ are very similar tools. The
difference is in how the syntax of the language looks before it is parsed and
tokenized.
Delphi sometimes finishes its compile and link cycles faster than Borland C++
simply because Pascal is easier to parse, and because Delphi supports a simpler
and more flexible type of binary file (DCUs are easier to make than OBJs). Of
course, the incremental linker found in C++Builder does much to eliminate this
difference in performance.
Because C++Builder uses the same compiler as Delphi, the road is obviously at
least halfway open to finding compatibility between Delphi and C++. The next
stone on the path is laid by C++Builder's advantageous use of the Delphi VCL.
C++Builder uses the Delphi VCL in all of its standard projects. It therefore
needs to understand Delphi's types, objects, and syntax. This was made possible
in part by the fact that Object Pascal is similar in structure to C++, but
supports a subset of the features available in C++. (There are some features of
Object Pascal, such as sets, not supported by C++, but these are relatively
minor roadblocks that can be overcome through a judicious use of templates and
classes.)
The converse of the logic in the last paragraph is not true. There is no need
for Delphi to parse C++. Furthermore, Object Pascal is, in a sense, a subset of
C++. It is therefore easier for C++Builder to adapt to the features of Delphi
than it is for Delphi to support certain esoteric features of C++ such as
multiple inheritance or function and operator overloading.
Object Pascal is not radically different from C++ in the sense that BASIC is
radically different from C++. Serious languages like Object Pascal and C++ are
part of the same family of tools, whereas cute and cuddly Visual Basic is from
an altogether different planet.
C++Builder and Delphi share a common heritage in the way they construct
objects, handle variable types, and define methods. In particular, a Delphi
object's VMT is identical in structure to a C++ object that uses single
inheritance. Furthermore, Delphi supports all the calling conventions, such as
CDECL, PASCAL, and STDCALL, that are used with C++ functions and methods.
Therefore, there is no difference in the structure of a Delphi function or
method and a C++ function or method. They look the same on the assembler level,
as long as C++ is not using any "advanced features," such as function
overloading. (Remember that by default, 32-bit Object Pascal uses the fastcall
calling convention, which means that it passes some parameters to functions in
registers. This same convention is supported by C++Builder, or you can
explicitly use a different convention if you like.)
Finally, there is a direct parallel between most C++ types and the standard
Delphi types. For instance, a C++ int is identical to a Delphi
Integer. Other types, such as strings, are either identical, or very
similar. For instance, a C++ char * (LPSTR) is identical to a Delphi
PChar. Furthermore, a Delphi string is fully compatible with a char
*, and the C++Builder AnsiString type is designed to be compatible
with, and to mimic, a Delphi string. This parallel structure continues
throughout the two languages, and includes complex types such as structures
(records) and arrays. (What C++ calls a union, Delphi calls a variable record,
and so on.)
There are, however, some differences between the languages. For instance,
Delphi does not support templates, C++Builder does not support try..finally
blocks or with statements, and each language implements exception
handling and RTTI slightly differently. These differences are significant, but
they do not represent an impenetrable barrier erected between Delphi and
C++Builder.
In this section you have seen that Delphi and C++Builder both use the same
compiler and support a similar syntax and style of programming. These are the
key elements that make it possible to share code between C++Builder and Delphi.
Using COM to Link Delphi Code in to C++Builder
The last few sections have shown that it is trivial to link Delphi units and
components directly in to C++Builder. Another great way to share code between
the two tools is through COM and OLE. You might want to use COM and OLE if you
want to share your code not only between Delphi and C++Builder, but also between
Delphi and Visual Basic, or other OLE aware tools such as Word, Excel, Visual
C++, and so forth.
There are three key ways to link Delphi COM objects into C++Builder:
- 1. Use OLE Automation or dual interfaces.
2. Use Delphi 3.0 to create ActiveX controls, and then load the ActiveX
controls on the C++Builder Component Palette.
3. Write raw COM or OLE code to link in Delphi objects.
The first two methods are supported automatically by C++Builder. The third
method requires you to dig into the details of OLE.
One simple way to link in Delphi objects through COM is to use OLE
Automation. C++Builder fully supports OLE Automation, and it provides a simple
technique for accessing the methods of an automation object.
Automation objects can be accessed in one of two fashions: You can access
them through a COM IDispatch interface or through dual interfaces.
C++Builder has built-in support for IDispatch through the
CreateOleObject VCL function and the Variant template class. This
is the simplest method for accessing automation objects, and it is the one you
should use unless speed is an extremely important issue for you.
For instance, suppose you have a Delphi 3.0 IDispatch interface that
exports the following methods:
ITest = interface(IDispatch)
[`{1746E520-E2D4-11CF-BD2F-0020AF0E5B81}']
function Get_Value: Integer; safecall;
procedure Set_Value(Value: Integer); safecall;
function Get_Name: WideString; safecall;
procedure Set_Name(const Value: WideString); safecall;
procedure Prompt(const text: WideString); safecall;
procedure VarTest(var v1, v2, v3: Variant); safecall;
property Value: Integer read Get_Value write Set_Value;
property Name: WideString read Get_Name write Set_Name;
end;
Assume further that the DLL that contains this interface is referenced in the
Registry in association with a CLSID that has a ProgID called
"TestLib.Test".
Here is how you can retrieve the interface and call the Prompt
method from inside C++Builder:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Variant V = CreateOleObject("TestLib.Test");
V.OleProcedure("Prompt", "Beware the Jabberwock, my son!");
}
This code first retrieves an instance of IDispatch inside a variant.
It then uses a method of the Variant class to call the Prompt
method from the Delphi interface.
Here is how you would call the VarTest method:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Variant V1, V2, V3;
Variant V = CreateOleObject("TestLib.Test");
V1 = 5;
V2 = "Beware the Jubjub bird, and shun the frumious Bandersnatch!";
V3 = V;
V.OleProcedure("VarTest", V1, V2, V3);
}
Delphi users looking at this code should note that a Variant is a
C++ class declared in Sysdefs.h. It is not a simple type as it is when
used inside Delphi. This subject is covered in depth in Chapter 3, "C++Builder
and the VCL."
If you want to use dual interfaces in C++Builder, you can get fast access to
automation objects that reside in a DLL. You can also access local servers, that
is automation objects in an executable, more quickly via dual interfaces, but
the built-in overhead with local servers is so great that the improvement in
speed given by dual interfaces is less noticeable.
There are no built-in tools for accessing dual interfaces in C++Builder.
Obviously, it will be possible to use them (after all, you can do anything in
C++), but you will have to lay the groundwork yourself. (The basic technique is
similar to the one outlined shortly in the section titled "Mapping Virtual
Method Tables.")
-
NOTE: I cannot ship an example
of this kind of code with this book because Delphi 3.0 had not shipped when we
went to press.
Using Delphi ActiveX Controls in C++Builder
C++Builder has built-in support for ActiveX controls. You can load them into
the environment and use them just as if they were native components. To get
started, do the following:
- 1. Choose Component | Install from the menu.
2. In the Install Components dialog, select the OCX button to bring up
the Import OLE Control dialog.
3. All the registered components on your system will appear in the
Import OLE Control dialog, as shown in Figure 6.3. If you want to register a
new component, select the Register button in the Import OLE Control dialog.
4. After selecting the control you want to import, press the OK button.
5. Your new control will now be listed at the bottom of the Installed
Components dialog, and an interface source file will be placed in the LIB
directory.
6. Press OK to recompile CMPLIB32 and add your control to the
Component Palette. By default, the new ActiveX control will appear in the OCX
page of the Component Palette.
FIGURE 6.3. The
registered components as seen when adding an ActiveX control to BCB.
If you don't want to use the previously shown automated method, you can also
use the C++ language to build your own OLE containers. This is, of course, a
difficult process, and the technique outlined earlier is usually infinitely
preferable. Don't forget that you can use the tools in Borland C++ to create OLE
containers, and then link that code in to your C++Builder project either
directly or through libraries or DLLs.
Using the Windows API to Link in Delphi COM and OLE
Objects
Delphi 3.0 is an extremely powerful tool for creating COM and OLE objects.
You can use it to build automation objects, dual interfaces, ActiveX controls,
and all manner of simple (or complex) COM objects.
C++Builder does not have the built-in support for COM that you find in Delphi
3.0. However, it does have access to all the OLE code you find in Borland C++
5.x, the Windows SDK, the MSDN, or in commercially available books and
libraries. It will access most of the key OLE code found in MFC or the new
ActiveX Template Library.
The following is a definition for a simple Delphi COM object that will
compile in either Delphi 2.0 or Delphi 3.0. For that matter, it will also
compile fine under BCB.
const
CLSID_ITable: TGUID = (D1:$58BDE140;D2:$88B9;D3:$11CF;D4:($BA,$F3,$00,$80,$C7,$51,$52,$8B));
type
ITable = class(IUnknown)
private
FRefCount: LongInt;
FObjectDestroyed: TObjectDestroyed;
Table: TTable;
public
constructor Create(ObjectDestroyed: TObjectDestroyed);
destructor Destroy; override;
function QueryInterface(const iid: TIID; var obj):
HResult; override; stdcall;
function AddRef: Longint; override; stdcall;
function Release: Longint; override; stdcall;
{ interface }
procedure Open; virtual; stdcall;
procedure Close; virtual; stdcall;
procedure SetDatabaseName(const Name: PChar); virtual; stdcall;
procedure SetTableName(const Name: PChar); virtual; stdcall;
procedure GetStrField(FieldName: PChar; Value: PChar);
virtual; stdcall;
procedure GetIntField(FieldName: PChar; var Value: Integer);
virtual; stdcall;
procedure GetClassName(Value: PChar); virtual; stdcall;
procedure Next; virtual; stdcall;
procedure Prior; virtual; stdcall;
procedure First; virtual; stdcall;
procedure Last; virtual; stdcall;
function EOF: Bool; virtual; stdcall;
end;
Here is the same interface as it would be declared inside a C++ project:
class IDBClass : public IUnknown
{
public:
STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE;
STDMETHOD_(ULONG,AddRef) (THIS) PURE;
STDMETHOD_(ULONG,Release) (THIS) PURE;
// Interface
STDMETHOD_(VOID, Open) (THIS) PURE;
STDMETHOD_(VOID, Close) (THIS) PURE;
STDMETHOD_(VOID, SetDatabaseName) (THIS_ LPSTR Name) PURE;
STDMETHOD_(VOID, SetTableName) (THIS_ LPSTR Name) PURE;
STDMETHOD_(VOID, GetStrField) (THIS_ LPSTR FieldName, LPSTR Value)
PURE;
STDMETHOD_(VOID, GetIntField) (THIS_ LPSTR FieldName, int * Value)
PURE;
STDMETHOD_(VOID, GetClassName) (THIS_ LPSTR Name) PURE;
STDMETHOD_(VOID, Next) (THIS) PURE;
STDMETHOD_(VOID, Prior) (THIS) PURE;
STDMETHOD_(VOID, First) (THIS) PURE;
STDMETHOD_(VOID, Last) (THIS) PURE;
STDMETHOD_(BOOL, EOF) (THIS) PURE;
};
This code is shown on the CD that accompanies this book in the program called
ComTable. Given the preceding definition, you could write the following C++ code
to use the Delphi object in C++:
#pragma argsused
void Window1_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
char CR[2] ="\r";
HRESULT hr;
PIDBClass P;
LPSTR S, Temp;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_IDBClass, NULL, CLSCTX_INPROC_SERVER,
IID_IUnknown, (VOID**) &P);
if (SUCCEEDED(hr))
{
S = (char *)malloc(1000);
Temp = (LPSTR)malloc(100);
P->SetDatabaseName("DBDEMOS");
P->SetTableName("COUNTRY");
P->Open();
strcpy(S, "Countries and their capitals: ");
strcat(S, CR);
strcat(S, CR);
while (!P->EOF())
{
P->GetStrField("NAME", Temp);
strcat(S, Temp);
strcat(S, ": ");
P->GetStrField("CAPITAL", Temp);
strcat(S, Temp);
strcat(S, CR);
P->Next();
}
MessageBox(hwnd, S, "COUNTRY TABLE", MB_OK);
P->Release();
free(S);
free(Temp);
}
}
This code is found on disk in the program called CRunTable. Clearly this
technique takes a bit of work, but it is not overly difficult. If you understand
something about COM objects, it provides a viable method for sharing code
between Delphi and C++.
In the ComTable directory on the CD that accompanies this book, you
will find two sample programs: one called ComTable and the other called
CRunTable. ComTable is a Delphi DLL that contains the COM object shown earlier
in this section of the chapter. You can compile this program with Delphi 2.0 or
with the command-line version of the Delphi compiler called DCC32.EXE
that ships with C++Builder.
In the same directory as the source for ComTable, you will find a file called
Win32.reg, which can be used to register ComTable with the system. In
this file you will find an entry called InProcServer that will have a
path to ComTable.dll hardcoded into it. You will have to edit this path
to point to the place on your system where ComTable.dll resides. You
can load the Win32.reg program into the Windows utility called
RegEdit.exe or go to a DOS window, change to the ComTable
directory, and type START WIN32.REG.
After compiling and registering ComTable, you can use BCB to compile and run
CRunTable. Click once with the left mouse button on the surface of this
application to load the Delphi COM object. CRunTable happens to be a Windows API
program rather than a standard BCB RAD program, but the core code that launches
the COM object also works in a standard BCB program. An sample BCB program of
this type called BCBRunTable can be found on the CD that accompanies this book.
The code found in ComTable provides a good example of how to construct a
simple COM object. Unfortunately, there isn't enough room in this book to
discuss this topic in any depth. If you want to learn more about this subject,
read my book Delphi 2 Unleashed (published by Sams), or go to
www.microsoft.com and download the COM specification, which turns
out to be a surprisingly readable document.
Using DLLs to Link Delphi Code in to C++Builder
C ++Builder can easily access a Delphi DLL containing functions and
procedures. If you want to access a Delphi object implemented inside a DLL from
C++Builder, you should read the following section called "Mapping Virtual Method
Tables."
If you have a Delphi DLL that you can call from inside a Delphi 2.01 project,
you do not need to change it at all to call it from inside C++Builder. To get
started, take the Delphi unit that lists the methods in your DLL and add it to
your C++ project. C++Builder will automatically compile the unit and generate a
C++ header file. In particular, you should link the unit using the techniques
described previously in the section called "Ground Rules for Linking Delphi Code
in to C++Builder."
You will not be able to access data declared in your DLL without first
calling a function or procedure. This same limitation applies to all DLLs,
regardless of the language used to implement them.
DLLs are useful when you have a large project that you want to divide into
several modules. By placing code in DLLs, you can divide your projects into
several binary files that can be loaded in and out of memory at will.
Mapping Virtual Method Tables
Delphi objects stored in a DLL are normally out of reach of Ebony projects.
However, there is a way to get at them by matching the VMT of the Delphi object
to the VMT of a virtual abstract, or "PURE," C++ object.
An example of this type of program is found on the CD that accompanies this
book in the directory called ObjectsInDLL. The code for this project is
described in the remainder of this section of the chapter. To run the program,
use Delphi or the copy of DCC32.exe that ships with BCB to compile
ObjectLib.dpr:
dcc32 -b ObjectLib.dpr
Then load the GetObject project into BCB and compile and run the program.
After the program is launched, you can press the button in the BCB program in
order to call one of the methods in the Object Pascal DLL.
Consider the following Delphi declaration:
TMyObject = class
function AddOne(i: Integer): Integer; virtual; stdcall;
function AddTwo(i: Integer): Integer; virtual; stdcall;
end;
A virtual abstract version of the same object could be declared in C++ like
this:
class __declspec(pascalimplementation) TMyObject: public TObject
{
public:
virtual _stdcall int AddOne(int i) = 0;
virtual _stdcall int AddTwo(int i) = 0;
};
To match up the VMT of the C++ object to the VMT of the Pascal object, all
you need to do is retrieve a pointer to the object from the DLL. One way to do
this is to export a function from the DLL that is designed for this explicit
purpose:
function CreateObject(ID: Integer; var Obj): Boolean;
var
M: TMyObject;
begin
if ID = ID_MyObject then begin
M := TMyObject.Create;
Result := True
end else begin
M := nil;
Result := False
end;
Pointer(Obj) := M;
end;
exports
CreateObject name `CreateObject';
You can call this method from the Ebony project with the following code:
typedef Boolean (_stdcall *LPCreateObject)(int ID, void *obj);
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TMyObject *MyObject;
LPCreateObject CreateObject;
HANDLE hlib = LoadLibrary("OBJECTLIB.DLL");
CreateObject = (LPCreateObject)GetProcAddress(hlib, "CreateObject");
if (CreateObject(1, &MyObject))
{
int i = MyObject->AddOne(1);
ShowMessage((AnsiString)i);
}
FreeLibrary(hlib);
}
The code shown here first declares a pointer to a function with the same
signature as the CreateObject routine in the DLL. It then calls the
standard Windows API functions LoadLibrary and GetProcAddress
in order to retrieve a pointer to CreateObject. If the call succeeds,
you can call CreateObject, return the address of the object you want to
call, and then call one of its methods.
Notice that all the methods in the Pascal object are declared virtual. This
is necessary because you want to match up the VMTs, or Virtual Method Tables, of
the two declarations. If the methods weren't declared virtual, there would be no
Virtual Method Table and the ploy would not work. You can declare nonvirtual
methods in your object if you want, but you will not be able to call these
methods from C++.
Note also that the order of the methods you declare is very important. The
names of the methods you want to call are not important to the C++
implementation; all that matters is the order in which the methods are declared.
You can use this same technique, or one like it, to export any object from a
Delphi DLL into a C++Builder or Borland C++ project. You do not have to use
GetProcAddress and LoadLibrary, but could instead import a Delphi
unit that exports CreateObject directly into your C++Builder project.
However, I use GetProcAddress in this example because it forces you to
imagine the exact steps necessary to make this process work. In other words, it
forces you to think about the addresses that are being shared between the DLL
and the BCB project.
Summary
In this chapter you have learned that C++Builder has an unprecedented
capability to access the code from its sister product, Delphi. As a rule, you
can simply link Delphi code directly into your C++Builder projects without any
additional work on your part. It is no harder to link Object Pascal code into
C++Builder than it would be to link Object Pascal code into a Delphi project!
The types of code that can be shared between Delphi and C++Builder include
forms, components, ActiveX controls, and simple methods and functions. You can
simply link this code directly into your projects without any extra work.
This chapter also examined using OLE or DLLs to share code between C++Builder
and Delphi. OLE can be useful if you want to share code with not only
C++Builder, but also with other, non-Borland environments such as Word, Visual
Basic, or Excel. DLLs are a great way to share code if memory management issues
are significant. In particular, you can load and unload DLLs from memory during
the life of your project.
|