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.
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.
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 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).
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.
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.
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.
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.)
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.
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.
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.
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.