t e m p o r a l 
 d o o r w a y 

Migrating To C++ Builder 3

 

Introduction

Migrating to C++ Builder 3 requires a number of changes in your thinking, especially if you are a component developer. This is not the same as C++ Builder 1, not by a long shot.

Run-Time Packages

The new component model allows - no, requires - you to develop each component in a separate package[footnote 1]. A package is essentially a .dll, but it produces a .bpl file. The .bpl file must be on the search path as defined by the PATH environment variable. This document does not presume you use run-time packages, but if you do, you must have the directory used for your component package in the PATH, the appropriate option needs to be checked in the project options, and you must remember to deploy the .bpl file to your users. You can also statically link the package to your application, but the .bpl will still be needed for design time interaction.

Getting Ready

  1. Install C++ Builder 3.
  2. Make sure you have any patches from Inprise.
  3. Install any non-ActiveX third party controls.
  4. Install any ActiveX third party controls. An example documented at this site is the installation of the MapX ActiveX control.
  5. Prepare your migration directory structure and decide on naming conventions. This will be discussed below.
  6. Migrate components to the new environment. Install and test them. If you have components based on ActiveX components, you may wish to leave their installation until you have done some conventional components, since there are other issues with ActiveX component descendants under the new version. Note that the sequence of component installation is sensitive to any dependencies between them.
  7. Migrate applications to the new environment.

Preparing For Migration

The hardest thing to do in any new development system is to establish a good directory structure and naming conventions. Usually you are required to do this before you understand the implications of your choice, and by the time you discover problems with your decision, it is so difficult to shift that an improved structure is never attained.

All I can do here is tell you about the choices I made and why. Hopefully this will help you think clearly about your own alternatives.

My previous structure was not too bad. I had a directory for components, a directory for non-component classes used by components and projects, and a directory hierarchy for projects, based on the inheritance hierarchy for those projects.

Each file name was kept identical to the class name implemented in the file. There were a few cases where that rule was not followed, due to some bug in Borland make which forced recompile of certain files even when that was not necessary - apparently based on file name. At any rate, I decided to alter the file names to strict compliance with class name as they were migrated. I also decided to maintain the directory structure. But I quickly found I would need to change my naming convention.

Packages alter one's thinking on components. Where a component used to consist of a .h and .cpp with .obj, it now consists of a .h, .cpp, .bpk (package make), a package dll .cpp, .bpi (import library), .bpl (package dll), and, annoyingly, a very very large .tds (about 7Mb per package on my system). Where before you had only to worry about the name of the .cpp, you now also have to worry about the name of the project, as with any application.

To keep things simple and readable in the project manager, which now is required for component development, I decided to name the project <classname>Package (which name affects the package.bpk and .cpp, etc), and the component <classname>Component.

Is This A Pain, Or What?

The trade-off with this change in component development is basically this:

Feature Benefit Drawback
Component goes in a package Only the package needs to be recompiled when a change is made to the component rather than rebuilding the entire component library. This is fast. Very fast. A package is a project. Developing a project is more complex than building a component under C++ Builder 1 was. Further, 100 separate packages may be a Gb of debugging files.
A package produces a .dll Component code is shared between applications if run time packages are used. This reduces memory and resource requirements on the destination system for heavyweight components used in multiple applications. DLLs have their own run-time overhead. If lots of lightweight controls are instantiated from runtime packages it is not clear that this strategy offers performance or resource conservation benefits.

If you build or frequently modify components, then the fast turnaround of component changes is an advantage, and, presumably, the automation of component creation by the IDE is sufficient to overcome the additional complexity.

On the other hand, if you seldom create your own components, you may find the additional complexity daunting. Hang in there - it isn't that hard, and the migration of your own components from C++ Builder 1 to C++ Builder 3 will be enough to get you used to the problems.

Starting The Migration Of Components

Migrate independent components first - the components which are used or derived by other components. This simplifies your task, especially while you are learning the new style of development.

Make copies of your C++ Builder 1 directories and alter the structure to reflect any changes you've decided to make. Work with the copies.

For each component, follow these steps:

  1. Create a new package, name it, and save it.
  2. Add the old component (previously renamed if you've changed your naming convention) to the package using Component | Install Component; you will be asked into which package you want to install the component - the answer is this one.
  3. Change the class definition to "class PACKAGE ..." in the .h
  4. Change the Register function to "void __fastcall PACKAGE Register()" in the .cpp
  5. Add "#pragma package(smart_init)" to the .cpp. This is essential. The documentation implies you don't need this unless you have multiple components in a package or source module. That is not true. You must have this statement, or your component will not show up on the component palette.
  6. Make the package.
  7. Component | Install Package to install the compiled package.
  8. Once done, the new component should show up on the palette. If not, go back and check everything. There's also a section in the Help on "Components, converting from CBuilder 1.0" which covers what you may have done wrong.

Some Signs Of Error

  • If you are converting a component which depends on another component, and you get a message "Unresolved external <usedcomponent>:: ..." then the <usedcomponent> doesn't have the PACKAGE modifier in the class declaration.

Testing

It's not a bad idea to test the newly installed component right away. Why wait? Have a Test directory under the Component directory, and create a test project for each component.

Grouping Your Components To Make Them Acceptable To The IDE

I have a library of 50 or so components. Some of them inherit from others. Some of them use others as part of an aggregate. One is based on an ActiveX control. As the migration of these components progressed, it became clear that there was sonething wrong with my initial strategy to convert each component to a separate package. Here's what I tried and what happened...

Strategy 1:

Follow the book. Add "PACKAGE" to class and Register() declarations. Add "#pragma package (smart_init)" to every component .cpp. Create a package for every component using the Install Component dialog, and add the component; use a good description of the component for improved documentation. Combine the packages into a project group for documentation and access purposes.

Result of Strategy 1:

Random Invalid Page Faults in the run time library CP3240MT.dll when a component is being installed. Some components install correctly. Deinstalling all of my component packages and then reinstalling the problem package goes fine - the abort then shows up on the installation of some other package. This suggests that the problem is not in the specific package, but is probably a C++ Builder bug or an operating system issue.

Strategy 2:

Use Strategy 1, but change the description of the components to be shorter - in fact, to the name of the class. This makes it easier to keep track of which components have been loaded and which haven't, to make absolutely sure there is no problem that can be isolated to a specific component. Also, some systems have problems with long strings in certain contexts - perhaps this is one.

Result of Strategy 2:

Now I get an error message on the component installation which states that one component package includes something already in another installed component package and that therefore I won't be allowed to install it (very suggestive that the previous aborts were caused by a reaction to the length of the package description). Inspection of the "requires" for the package shows it contains a reference to the specified .bpi (which it needs, but the message says take it out). I remove that reference. I build the package. The build tells me I need the reference I already removed from the requires. I tell it not to add the reference. I try to install the package. The IDE then claims the package still contains the reference that was removed. I check the package. The reference is definitely not there. I delete the compiler and linker output files and build again. The IDE still claims that the package to be installed contains the reference. But the only thing in the package is the include file for the absolutely necessary headers.

Strategy 3:

Make a single package containing all of the component units. Build the package and install it. Accept the things it wants to add to "Requires".

Result of Strategy 3:

It works. The package can be installed, the components show up on the palette.

Conclusion (?)

There is some sort of bug in the handling of a) large number of separate component packages (i.e. > 20) and b) long descriptions for component packages. There is also a bug in the way the IDE is dealing with intercomponent / interpackage references, which includes a) not resolving duplicate .bpi imports in some contexts (it is not clear to me what those contexts are, since clearly all component packages probably reference VCL packages, and yet there is no problem with that duplication) and b) believing those .bpi imports are present when they have been removed (possibly based on the presence of an include in the component source header file).

None of these restrictions is documented. The documentation seems to clearly indicate that each package should contain a component and that any other organization is the exception rather than the rule (see the help file on "Components,Converting components from CBuilder 1.0").

But there is an upside. Strategy 3 compiles and makes much faster than any of the others when you need to remake all of your components. A 20 member program group takes over a half hour to rebuild or remake, even when the average compile is around 20 secs. The strategy 3 package takes only a fraction of that time.

Application Migration

This isn't terribly difficult if you correctly migrated your components. Backup your application and its project. Then open the project in CBuilder 3. You will be notified that it is being converted.

C++ Builder will not change the name of the project from .mak to .bpr when you save it, nor will it allow you to save the project as a .bpr extension file. So once you have opened the project, save it, and then exit C++ Builder, and rename the project to have the .bpr extension.

Make sure all of your paths and run time component search paths and package names are correct, otherwise you may get unusual errors when loading, compiling, or linking your application project.

What Can Go Wrong

  • Open all of the forms in the project. It is possible that you might not have placed #pragma package (smart_init) in all of your components, and if not, you will get a message that some component cannot be found. This can happen even if the component is part of the component project. If you get the message, exit the project, open the component package project, and check the component source for the #pragma. If it's not there, change it, rebuild the package, and then try your form again.
  • In C++ Builder1 either there was more protection against access violations in Loaded() or Loaded() was not always called. In any case, if you get an access violation in your component package while trying to open the form, you will need to follow several steps: 1) Close everything, 2) Open the form as a .dfm file, 3) Make a list of all of your components in the form, 3) Create a new form. Repeat the following - add one of the candidate components, view the form as text, view the form as a form. If the abort occurs, that is the offending component, otherwise, repeat with the next component. This is not foolproof, for instance if some action occurs based on the initialized state of the component, but if this test doesn't work, then you can copy and paste components from your application form .dfm to this test form and follow the same process until you get an abort. Once you find the error, addition of a try / catch (...) or a specific try / catch block can protect against the error. Rebuild the component package and reopen the application package, and you should be able to open the form - unless there's yet another component with a problem.

Footnotes

1. Or so it seemed at the time. New information suggests this is not the case.

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved