TOCBACKFORWARD

Charlie Calvert's C++ Builder Unleashed

- 6 -

Sharing Code Between Delphi and C++Builder

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:

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:

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:

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.

TOCBACKFORWARD


©Copyright, Macmillan Computer Publishing. All rights reserved.