July 1997

Using Delphi components in C++Builder

by Kent Reisdorph

One of C++Builder's most promising features is that it lets you use your existing Delphi components. In many cases, you can use these components as-is. Sometimes, however, a Delphi component simply won't work in C++Builder. You may get compiler errors when you add the component to the Component Palette. Or the Delphi component may install without incident, but give you compiler or linker errors when you try to build the application that uses the component.

In this article, we'll point out some problem areas you may encounter when you're installing Delphi components in C++Builder. We'll first examine the most common (and most obvious) problems and also describe some of the more subtle difficulties that aren't as easy to resolve.

How C++Builder handles Delphi components

When you install a component in C++Builder, C++Builder looks first at the component's file extension. If it's OBJ, then C++Builder simply links the object file into the component library. However, if the file extension is CPP, then C++Builder invokes the C++ compiler to produce an object file that it can link to the component library.

If the file extension is PAS, C++Builder invokes the Pascal compiler, which is slightly different from the one in Delphi. In C++Builder, the Pascal compiler will produce not only the DCU file for the component, as Delphi does, but will also produce an OBJ file and a header file with the extension HPP. The object file for the Pascal unit is necessary so C++Builder programs can link the component's code to the C++Builder executable file. The header file lets your application see the component's class declaration.

If you think about this process, you may shake your head in wonder. You have a Pascal source code unit that's compiled into an object file, which is compatible with C++ code. Not only that, C++Builder creates a header file for you so you can treat your Pascal classes as if they were C++ classes. All you have to do is add the Delphi component to the Component Palette, and C++Builder takes it from there.

Amazing! Well, it is amazing when it works (and it works 90 percent of the time), but there are still a few minor details to deal with. Some Pascal language features just don't translate well to C++, and those problem features can cause you headaches if you don't handle them properly.

As you're installing Delphi components in C++Builder, you may encounter problems on at least three fronts: while compiling the Pascal code when you install the component on the C++Builder Component Palette; in the headers generated by C++Builder (these problems won't show up until you try to use the component in a C++Builder application); and while linking a C++Builder application using your Delphi component. Let's examine these areas individually.

Pascal compiler issues

When you add a Delphi component to the Component Palette, C++Builder invokes the Pascal compiler to compile your component. You may encounter compiler errors for components that installed fine under Delphi, because Borland deemed some Pascal language features undesirable for support in C++Builder (with good reason, in most cases).

For the most part, these are legacy language features left over from TurboPascal days. It's unlikely that you'll use these features in your code--but we won't leave anything to chance. Let's look at three language features C++Builder doesn't support.

The Real data type

Simply put, C++Builder doesn't support the Real data type. If you have any Real variables in your component, you'll get the error Unsupported language feature: Real when you attempt to install the component to the Component Palette. In most cases, you can change any Real variables to Double variables to avoid negative ramifications.

The Pascal object model

Early versions of ObjectPascal included the object model. The object was the forerunner to the ObjectPascal class. If you have legacy code you've been carrying forward through the years, you may have some old object-model objects kicking around.

The fix for this problem isn't nearly as easy as that for the Real data type problem--you must take any code that uses the object model and convert the objects into classes. Doing so is a bit of a pain, since the object and class modules handle several items differently (de-referencing pointers, for instance).

Empty sets

At TurboPower Software, we like to create components with a fairly high level of abstraction. That abstraction manifests itself when we create constants to use as default values for properties. Later, if we want to change the default behavior of a particular component, we need to change only the constant declarations--and that change is reflected throughout the code.

In most of our units, this design goal yields const sections of code which look like the following:

const:
  DefComNumber = 0;
  DefBaud = 19200;
  DefParity = pNone;
  DefDatabits = 8;
  DefStopbits = 1;
  DefHWFlowOptions = []; 
Note that the last line creates a constant that's an empty set. For some reason, C++Builder's Pascal compiler can't handle that particular declaration. The result is the compiler error Error: Unsupported language feature: 'initialized set > 4 bytes in size'. Chalk this problem up to the mysteries of trying to combine two languages in one development environment. The moral of this story is that you shouldn't use empty sets in const declarations.

Header-generation issues

Once you've installed a Delphi component on the Component Palette, you may still run into problems when you try to use the component in a C++Builder application. You won't encounter some of these pitfalls until you try to use the header that C++Builder generates for your Delphi component. When the C++ compiler tries to compile your header, it may hit snags that produce C++ errors. Let's take a look at some examples.

Variant records

A record in Pascal is synonymous with a structure in C++ (except that C++ structures can have functions, but that's beside the point). In Pascal, part of a record may exist as a variant portion. A record that has a variant section is called a variant record and looks like this:
TMyRecord = record
  Ch : Char; 
  Cmd : Byte; 
  X, Y : Byte; 
  case Byte of
    1 : (Other : array[1..11] of Byte);
    2 : (OtherStr : String[10]);
  end;
end;
This record's final data member can be either an array of bytes called Other or a Pascal short string called OtherStr.

Note that a record will be the same size, in bytes, regardless of the presence of variant record members. Ultimately, the size of the record will be the sum of the sizes of the non-variant members of the record plus the size of the largest of the variant members. The compiler figures out which variant member is the largest and uses that size to determine the overall size of the record.

C++ supports the concept of the variant record, albeit with different terminology. In C++, a structure may contain an anonymous union. The C++ equivalent to the Pascal record we just discussed would be as follows:

struct TMyRecord
{
  char Ch;
  Byte Cmd;
  Byte X;
  Byte Y;
  union
  {
    System::SmallString<10> OtherStr;
    Byte Other[11];
  };
};

If you try to compile a program containing this code, you'll get a compiler error that states Union member TMyRecord::OtherStr is of type class with constructor.

The bottom line is that a class with a constructor can't be used in a union. But where does the System::SmallString<10> come from in the code snippet? The answer is that C++Builder implements the Pascal short string as a template, a template is a class, a class typically has a constructor, but a class can't have a constructor in a union. End game.

It's not just the Pascal short string that suffers this fate. The Pascal long string, Comp, and Currency data types are also implemented as classes in C++Builder. As a result, you can't use any of these types in the variant part of a variant record. The only way to work around this problem is to design your Pascal records accordingly.

Multiple constructors

You can use multiple constructors in your Delphi components, but you need to take care how you do so. In Pascal, you distinguish multiple constructors by using a different name for each constructor. In C++, all constructors must have the same name (the name of the class itself). The only way to distinguish constructors in C++ is to create a unique parameter list for each constructor.

Take the following Pascal constructors as an example:

constructor Create(Owner : TObject; X : Integer); constructor CreateEx(Owner : TObject; Y : Integer);
At the same time C++Builder creates a header for the Pascal unit in which you use these constructors, it will generate the following declarations:
__fastcall MyComponent(TObject* Owner, int X);
__fastcall MyComponent(TObject* Owner, int Y);
Note that these declarations contain exactly the same number of parameters, and that the parameters appear in the same order.

If you try to use this component in a C++Builder application, you'll get a compiler error. In order to compile this component, you'll have to modify one of the constructors so that the two constructors have unique parameter lists. Doing so may be as simple as adding a dummy parameter to one of the constructors. Be sure that all your constructors have unique parameter lists, including those in base classes.

Properties of arrays

In Pascal, it's legal to return an array by value from a function. In C++, you can't return an array from a function--you can return a pointer to an array, but you can't return an array by value.

Many properties use read and write methods to perform special processing when the property is read or written to. A read method for a property must return the value of the data member that's storing the property's data. Since it's illegal to return an array by value in C++, you should avoid using a property that has an array as the property type. This isn't to say that you can't have array properties--array properties are something entirely different. (It's confusing, I know.)

More const issues

Earlier, I mentioned that we use a lot of const declarations in our development here at TurboPower Software. Besides the case mentioned previously, we've run into a couple of other situations C++Builder can't handle properly.

Take this line of code, for instance:

const
DefPointer = nil;
This declaration causes C++Builder to generate the following declaration in the header for this unit:
const void * DefPointer = (void *)(0x0);
Although this declaration looks bizarre, the greater sin is that this const declaration appears in a header file.

What's the implication? Let me answer with an illustration. Let's say you have a project that has multiple source code units. In addition, more than one of those source units #includes the header containing this declaration. The application will compile with no problems, but it will fail at link time, complaining that the variable DefPointer is declared in one unit and again in another unit.

I don't know why this particular declaration is translated into a const declaration rather than a #define. Perhaps Borland will fix this problem in future versions of C++Builder. For the time being, you can work around it by declaring the constant as follows:

const DefPointer = 0;
Another problem with const declarations is less severe. Let's say you have a library of components with several units, and one of those units contains this declaration:
const DefaultWidth = 100;
So far, so good. But now suppose that the following declaration appears in another unit:
const DefaultWidth = 50;

If you use both of these components in a C++Builder application, you'll get the linker warning Redeclaration of DefaultWidth is not identical. While this is just a compiler warning, not an error, remember that anyone who uses your components will see this warning, too. To fix this minor problem, make sure that your constants have unique names, even if they're contained in separate units.

Some scenarios leading to linker errors

Finally, we've come to the type of error that's sometimes the most frustrating of all: the linker error. In the previous section, I describe a situation that will produce linker errors if you don't remedy it. Let's examine two other issues that will also lead to linker errors.

Pascal class methods

The Pascal class method is a bit of an oddity. You declare a class method within a class using the class keyword. You can call this type of method without first creating an instance of the class in which you declare the method.

For example, let's say you have a DoIt class method in a MyClass Pascal class. You could write this line of code:

MyClass.DoIt;
It's hard to tell much from this code snippet, but you'll see that I didn't create an instance of MyClass before I called the DoIt method.

C++ doesn't have a true equivalent to the Pascal class method--the nearest is a static class member function. (In Delphi, constructors are class methods.) However, implementing static member functions in C++ is different than implementing class methods in Delphi. For example, in Delphi, a class method can use the Self keyword, but in this context it represents the class type and not a class instance.

Borland's engineers worked out a way to deal with Pascal class methods in C++Builder. Unfortunately, the technique fell short of its goal. The function declaration generated by C++Builder for a Pascal class method will let you compile an application, but that method fails at link time.

The best thing you can do is avoid using class methods as public methods in your components. Note that it's fine to use class methods that you reference from your own code--the problem arises when users try to use a class method from a C++Builder application.

HWND versus hWnd

Of all of the problems the Delphi programmer might face when writing components for use in both Delphi and C++Builder, HWND versus hWnd may be the most treacherous. Throughout the development of C++Builder, Borland engineers were plagued by the differences between the way VCL handles the HWND data type and the way the rest of the world handles this type. In VCL, the hWnd type is declared as an Integer. In the 32-bit C and C++ world, HWND is declared as a void pointer. In one specific case, this difference causes a big problem for component writers.

Suppose a component has a public method that takes a window handle as a parameter or that returns a window handle. For the sake of argument, let's say the function is declared in the Pascal source code as follows:

function GetHandle : hWnd;

This function returns an hWnd (or HWND or hwnd or... it doesn't really matter in Pascal).

When you install this component, C++Builder will generate a header with the following declaration:

HWND GetHandle();

This component will install without incident and may even appear to work in a C++Builder application. However, if you attempt to call the GetHandle() function for this component, you'll get a linker error. The linker is looking for a function that has a void* as its return type, but the function in the component's OBJ file was created with an int as its return type. The linker will never be able to find this function because of the difference between the VCL and C++ versions of HWND.

A couple of solutions exist for this problem. One is to use the Integer data type anywhere you'd normally use hWnd. However, the code can be confusing to read later. Another solution is to declare a new type that does essentially the same thing as hWnd:

type TMyHwnd = Integer;
Now you can use TMyHwnd anywhere you'd typically use hWnd. The advantage to this method is that anyone reading your code will have a clue that something special is going on with this type.

Note that what applies to HWND also applies to HINSTANCE. In VCL, hInstance is declared as a LongInt. In C and C++, HINSTANCE is a void*. Be sure you take this difference into account if you have public functions that use HINSTANCE.

Beware the alignment!

And now, one final word about using Delphi components in C++Builder. C++Builder expects that you'll build your Pascal source code with the default data alignment setting: word alignment. (You control data alignment with the $A Pascal compiler directive.) If you try to use byte alignment, your component will experience strange and inexplicable behavior.

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.