Problems with Programming Qt from C++Builder 6 CLX Applications

By: Brian Long

Abstract: It is not possible to use the routines declared in the Qt.hpp header file (the CLXDisplay API) without generating a Stack Overflow. This article looks at why this is and how it can be prevented.

Problems with Programming Qt from BCB6 CLX Apps

Problems with Programming Qt from
C++Builder 6 CLX Applications

Brian Long (www.blong.com)

Table of Contents

Click here to download the file associated with this article.


Introduction

I recently found out from questions asked around in the Borland newsgroups (and also sent directly to myself) that C++Builder 6 seems to have something of a showstopper of a problem if you want to access the underlying Qt API (referred to as the CLXDisplay API) in a VisualCLX application written with it. Things are fine if you just use the VisualCLX components and their methods and properties, but if you need more control and need to dip into the Qt API (used to implement the VisualCLX components) you will come unstuck.

Due to an unfortunate limitation of the Delphi compiler the Qt.hpp header file supplied to surface the underlying CLXDisplay API proves completely unusable. A call to any CLXDisplay API routine will cause a recursive loop leading to a Stack Overflow exception.

This article explains the problem with the header, why it occurs and finally how it can be resolved. Since the number of changes to completely rectify the problem is so huge you can access a modified version of the header file that provides an unofficial fix until Borland supply an official patch.

On the way through we'll look at an additional CLXDisplay API problem that programmers using Delphi (versions 6 and 7), C++Builder (version 6) and Kylix (versions 1, 2 and 3) might encounter.

The CLXDisplay API

The Qt.hpp header file is intended to allow access to the CLXDisplay API. This API exposes the functionality of the Qt classes as individual "flattened methods". These are exported by the Qt interface library as a means of providing access to the real methods available in the Qt library. On Windows, C++Builder 6 uses a single DLL called qtintf.dll that contains the interface routines (flat methods) as well as the Qt classes themselves. On Linux, Kylix also uses a single library, libborqt.so by default, although #defining the symbol CLX_USE_LIBQT causes it to use a separate interface library (libqtintf-6.9-qt2.3.so) and a Borland-patched Qt library (libqt.so.2.3.0).

The reason for this intermediate layer is simply the fact that the CLX library was originally built up in the Delphi language, which cannot directly handle exported C++ classes. A way of overcoming this is to expose every method as a regular function, which explicitly has one additional parameter to take the object pointer. These flat methods in the interface library are mapped directly through to the corresponding methods in the real classes.

The flat method signatures match an explicit representation of how the this pointer is passed in a normal method call, but being functions they can be called by any programming language (assuming all the expected parameter types are compatible with that language). The Delphi VisualCLX source code uses the CLXDisplay API (in the Qt.pas unit) and C++Builder programmers are required to use it too (through the Qt.hpp header file) in order to access Qt objects directly. You can find out more about using the CLXDisplay API in Reference 1 and Reference 2 from the Further Reading section.

It is advisable to restrict your use of the CLXDisplay API to cases where the VisualCLX components do not offer the functionality you need. If there is a CLX solution to a problem you should always use that instead of overusing the Qt routines.

The Big Problem

The problem is that the Qt.hpp header file was manufactured incorrectly. It was created by the Delphi command-line compiler, dcc32.exe, which can generate C++ header files that correspond to the interface section of a Delphi unit being compiled. There are aspects of the Qt.pas unit that are not catered for correctly by this C++ header generation mechanism; aspects that are used hundreds or thousands of times throughout the unit, causing every function declaration to be unusable for one or sometimes two reasons.

In short the problem is that any attempt to call any routine in the CLXDisplay API through this header file results in infinite recursion (until a Stack Overflow stops it).

Why Did It Happen?

The Delphi to C++ translation support in the dcc32.exe works well in many cases, but not for DLL import declarations. There are some problems with the code that it generates. It might be best if we used a little example to highlight the issues.

DLL Imports

Take this simple Delphi import unit:

unit DLLImports;

interface

procedure Foo(I: Integer); cdecl;
procedure Bar(D: Double); cdecl;

implementation

const
  DLLName = 'The.DLL';

procedure Foo(I: Integer); cdecl;
external DLLName;

procedure Bar(D: Double); cdecl;
external DLLName;

end.

It provides declarations to allow Delphi code access to two C calling convention routines inside of some DLL. The routines as exported by the DLL are called Foo and Bar and the declarations make them accessible to Delphi code.

To generate the corresponding C++ header file you can add the unit to a C++Builder project and compile it, or run an appropriate command on the command-line. If using the command-line compiler you should use the -JPHNE command-line switch to ensure that Delphi generates a C++ object file (-JP), along with a header file (H) with a C++ namespace (N) containing declarations for all public items in the unit (E).

To ensure you get the correct behaviour it is advisable to use the Delphi compiler that shipped with C++Builder 6 (you will find it in the Bin directory) as opposed to with some other version of C++Builder, or even with a copy of the Delphi product. In particular the Delphi compiler shipped with Delphi 7 differes in its output from the one supplied with C++Builder 6


C:ToolsCBuilder6ProjectsCLX & Qt>toolscbuilder6bindcc32 -JPHNE DLLImports.pas
Borland Delphi Version 14.0
Copyright (c) 1983,2002 Borland Software Corporation
DLLImports.pas(21)
22 lines, 0.11 seconds, 66 bytes code, 4 bytes data.

C:ToolsCBuilder6ProjectsCLX & Qt>

The header file looks like this:

// Borland C++ Builder
// Copyright (c) 1995, 2002 by Borland Software Corporation
// All rights reserved

// (DO NOT EDIT: machine generated header) 'DLLImports.pas' rev: 6.00

#ifndef DLLImportsHPP
#define DLLImportsHPP

#pragma delphiheader begin
#pragma option push -w-
#pragma option push -Vx
#include <SysInit.hpp>	// Pascal unit
#include <System.hpp>	// Pascal unit

//-- user supplied -----------------------------------------------------------

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
//-- var, const, procedure ---------------------------------------------------
extern "C" void __cdecl Foo(int I);
extern "C" void __cdecl Bar(double D);

}	/* namespace Dllimports */
using namespace Dllimports;
#pragma option pop	// -w-
#pragma option pop	// -Vx

#pragma delphiheader end.
//-- end unit ----------------------------------------------------------------
#endif	// DLLImports

What the compiler has done is to generate declarations for each exported routine, specifying the name, parameter list and calling convention. It also marks each one as extern "C" to ensure that the compiler treats them as C routines and performs no name-mangling on them. This will allow references to these functions to map down to the like-named routines in the DLL's import library.

To be fair, routines compiled with the C calling conventions typically have an underscore prefixed onto their name. The C compiler works with this convention and so when you call a __cdecl routine calling Foo the linker will try and find a routine _Foo. These sample declarations are therefore not that realistic, since the suggestion from the Delphi import unit is that we have LL routines compiled with C calling conventions, but which don't have the underscore prefix. But let's not worry too much about that right now.

No problems so far, so what next?

Aliased Imports

Let's look at what gets generated for aliased imports now. An aliased import is an import declaration that allow Delphi code to access a DLL routine by a name other than the one it is exported with. Take this simple Delphi import unit:

unit DLLImports;

interface

procedure Foo(I: Integer); cdecl;
procedure Bar(D: Double); cdecl;

implementation

const
  DLLName = 'The.DLL';

procedure Foo(I: Integer); cdecl;
external DLLName name 'FooBar1';

procedure Bar(D: Double); cdecl;
external DLLName name '_FooBar2';

end.

The DLL routines are called FooBar1 and _FooBar2 respectively but they are being made available to program code as Foo and Bar.

The second DLL routine in this example is now named more like a typical C calling convention DLL export.

When run through the translator we get a header file containing this:

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
//-- var, const, procedure ---------------------------------------------------
extern "C" void __cdecl Foo(int I);
extern PACKAGE void __cdecl Bar(double D);
#pragma option push -w-inl
inline void __cdecl Foo(int I)
{
	FooBar1(I);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl Bar(double D)
{
	Bar(D);
}
#pragma option pop

}	/* namespace Dllimports */

Notice that now we are using import aliases the compiler generates an external declaration as well as an inline function wrapper. The intention is that the inline wrapper is named as the Delphi routine is named, and the external declaration should map down to the real DLL export.

You can see that the first external declaration still correctly uses extern "C", but it is called Foo, rather than FooBar (however with the lack of the leading underscore, maybe we can excuse the translator as it is difficult to decide how to correctly declare the routine). The second declaration is not declared extern "C" and so presumably will be name-mangled, and it also has the wrong name. It should be declared as FooBar2 (bearing in mind the fact that the underscore will be automatically prefixed by the compiler).

Now let's look at the inline wrappers. The first one correctly tries to call FooBar1, although that routine does not exist, so this will cause a compiler error.

The second one tries to call the second external declaration and gets the declaration name right (even though that name itself is wrong; it should be FooBar2, not Bar). However, since the inline wrapper has the same name as the external routine, all we get is the wrapper calling itself recursively until the inevitable stack overflow.

There is little point worrying about the problems with the first routine as it doesn't follow the correct naming conventions so let's focus on the second one. The recursive call could be resolved by ensuring the external declaration is generated correctly, and has a different name to the wrapper. In this case changing the code to this would work for it:

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
//-- var, const, procedure ---------------------------------------------------
extern "C" void __cdecl FooBar2(double D);

#pragma option push -w-inl
inline void __cdecl Bar(double D)
{
	FooBar2(D);
}
#pragma option pop

}	/* namespace Dllimports */

But that sort of approach will come unstuck with an import unit like this:

unit DLLImports;

interface

procedure Foo(I: Integer); cdecl;

implementation

const
  DLLName = 'The.DLL';

procedure Foo(I: Integer); cdecl;
external DLLName name '_Foo';

end.

This represents a DLL import declaration for a routine with an underscore prefix (typical C calling convention), but which is being made available to Delphi code as just Foo (Delphi doesn't implicitly add the prefix when looking for a cdecl routine). This will generate a header containing this:

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
//-- var, const, procedure ---------------------------------------------------
extern PACKAGE void __cdecl Foo(int I);
#pragma option push -w-inl
inline void __cdecl Foo(int I)
{
	Foo(I);
}
#pragma option pop
}	/* namespace Dllimports */

Now we have a situation where the recursion can't be fixed by simple correction of the inline wrapper and adding back the extern "C". The external declaration needs to stay with the same name since that is the name exported by the DLL (albeit without the leading underscore), so this means the inline routine could be renamed to solve the problem. This might be undesirable as then the programmer will have to learn new routines to call when they want to call Foo().

Alternatively the routines could be left with the same names, but to stop the recursion the external declarations could be placed in a different name space. The wrapper then specifies the namespace when calling the routine. That turns the above header into this:

namespace Dllimportsexterns
{
extern "C" void __cdecl Foo(int I);
}	/* namespace Dllimportsexterns */
namespace Dllimports
{
#pragma option push -w-inl
inline void __cdecl Foo(int I)
{
	Dllimportsexterns::Foo(I);
}
#pragma option pop
}	/* namespace Dllimports */

Again, notice the fix to the external declaration (extern "C") to stop the name mangling.

The program code calls the inline Foo function in the Dllimports namespace, which then call to the Foo function in the Dllimportsexterns namespace, which maps down to the corresponding DLL routine.

Things are slightly different if the import unit defines types that are used by parameters of the imported routines. In this case the external declarations in the new namespace require access to the original namespace, which defines those types. This can be accomplished with the using directive. For example, if the Delphi unit looked like this:

unit DLLImports;

interface

type
  TRect = packed record
    X, Y, W, H: Word;
  end;

procedure Foo(I: Integer; const R: TRect); cdecl;
procedure Bar(D: Double; var R: TRect); cdecl;

implementation

const
  DLLName = 'The.DLL';

procedure Foo(I: Integer; const R: TRect); cdecl;
external DLLName name '_Foo';
procedure Bar(D: Double; var R: TRect); cdecl;
external DLLName name '_Bar';

end.

then the correspond part of the generated header looks like this:

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
#pragma pack(push, 1)
struct TRect
{
	Word X;
	Word Y;
	Word W;
	Word H;
} ;
#pragma pack(pop)

//-- var, const, procedure ---------------------------------------------------
extern PACKAGE void __cdecl Foo(int I, const TRect &R);
extern PACKAGE void __cdecl Bar(double D, TRect &R);
#pragma option push -w-inl
inline void __cdecl Foo(int I, const TRect &R)
{
	Foo(I, R);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl Bar(double D, TRect &R)
{
	Bar(D, R);
}
#pragma option pop

}	/* namespace Dllimports */

To fix this header it should be changed something like the following:

namespace Dllimports
{
//-- type declarations -------------------------------------------------------
#pragma pack(push, 1)
struct TRect
{
	Word X;
	Word Y;
	Word W;
	Word H;
} ;
#pragma pack(pop)
}	/* namespace Dllimports */
namespace Dllimportsexterns
{

using namespace Dllimports;
//-- var, const, procedure ---------------------------------------------------
extern "C" void __cdecl Foo(int I, const TRect &R);
extern "C" void __cdecl Bar(double D, TRect &R);
}	/* namespace Dllimportsexterns */

namespace Dllimports
{
#pragma option push -w-inl
inline void __cdecl Foo(int I, const TRect &R)
{
	Dllimportsexterns::Foo(I, R);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl Bar(double D, TRect &R)
{
	Dllimportsexterns::Bar(D, R);
}
#pragma option pop

}	/* namespace Dllimports */

You might wonder why this problem hasn't been spotted before in the Delphi compiler. After all, Delphi has been generating headers for C++Builder since 1997. The issue is almost certainly that none of the headers generated for C++Builder involved units with import declarations. All the Delphi units that contain import declarations are dealing with the Windows API and C++Builder already has all it needs to work with the Win32 API in the Microsoft Platform SDK header files. Consequently the import declarations in all these Delphi import units is conveniently skipped during the process of building all the support files for C++Builder (this is done by use of the Delphi $EXTERNALSYM compiler directive in the import units).

Because of this the problem never showed up. In C++Builder 6 we have, for the first time, the need to expose APIs from a third party DLL and the limitations of the Delphi compiler shine through. The important question is why this problem with the Qt header file hasn't been spotted before. C++Builder 6 has been around since February 2002 (a year at the time of writing) and yet nothing. Maybe C++Builder programmers haven't tried out low-level Qt programming with VisualCLX apps since there was no corresponding Linux equivalent at the time (Kylix 3 was released in July 2003).

Anyhow, it seems that the Delphi bug was perhaps noticed although the C++Builder problem has so far not been fixed in any Update Packs. The Delphi 7 and Kylix 3 Delphi compilers now generate no inline wrapper routines; instead they try just to generate extern routines for all the import declarations in the original Delphi unit. As a consequence the Kylix 3 C++ Qt support is generated differently, and indeed has its own set of problems. You can find out about these and how to overcome them by reading Reference 4 in the Further Reading section.

Fixing The Problem

There are a total of 3366 import declarations in the Qt unit, all of which specify a specific DLL export name starting with an underscore prefix. This means that 3366 external declarations in Qt.hpp need to be modified (as appropriate) to match the real DLL routines. Additionally each routine needs to changed to extern "C" from extern PACKAGE, however this is easier to accomplish by wrapping an extern "C" {} section around the entire set of external declarations. After this, 3366 inline functions need to be fixed to call the appropriate external routines with a namespace specification to avoid any possible recursion. That's a lot of fixing by anyone's standards.

There are many cases where the Qt interface library exports multiple flat methods that correspond to overloaded methods in the Qt classes; about 640 in total. Each separate overloaded method is exported as a flat method with the same base name followed by a number to distinguish one from another. We will need to ensure that the overloaded inline wrappers all call the appropriate exported routine that expects the same parameters and apart from the sheer volume of changes, this is where things get tricky.

The basic principle is to change the header file from this original layout:

namespace Qt
{
...
extern PACKAGE void __cdecl QWidget_destroy(QWidgetH* handle);
...
extern PACKAGE void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */;
extern PACKAGE void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, int minw, int minh)/* overload */;
extern PACKAGE void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */;
extern PACKAGE void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, int maxw, int maxh)/* overload */;
...
#pragma option push -w-inl
inline void __cdecl QWidget_destroy(QWidgetH* handle)
{
	QWidget_destroy(handle);
}
#pragma option pop
...
#pragma option push -w-inl
inline void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */
{
	QWidget_setMinimumSize(handle, p1);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, int minw, int minh)/* overload */
{
	QWidget_setMinimumSize(handle, minw, minh);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */
{
	QWidget_setMaximumSize(handle, p1);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, int maxw, int maxh)/* overload */
{
	QWidget_setMaximumSize(handle, maxw, maxh);
}
#pragma option pop
}	/* namespace Qt */

to this fixed layout:

namespace Qt
{
...
extern "C"
{
namespace QtImports
{
extern PACKAGE void __cdecl QWidget_destroy(QWidgetH* handle);
...
extern PACKAGE void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, Types::PSize p1);
extern PACKAGE void __cdecl QWidget_setMinimumSize2(
  QWidgetH* handle, int minw, int minh);
extern PACKAGE void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, Types::PSize p1);
extern PACKAGE void __cdecl QWidget_setMaximumSize2(
  QWidgetH* handle, int maxw, int maxh);
...
} //namespace QtImports
} //end extern "C"
namespace Qt
{
...
#pragma option push -w-inl
inline void __cdecl QWidget_destroy(QWidgetH* handle)
{
	QtImports::QWidget_destroy(handle);
}
#pragma option pop
...
#pragma option push -w-inl
inline void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */
{
	QtImports::QWidget_setMinimumSize(handle, p1);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMinimumSize(
  QWidgetH* handle, int minw, int minh)/* overload */
{
	QtImports::QWidget_setMinimumSize2(handle, minw, minh);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, Types::PSize p1)/* overload */
{
	QtImports::QWidget_setMaximumSize(handle, p1);
}
#pragma option pop

#pragma option push -w-inl
inline void __cdecl QWidget_setMaximumSize(
  QWidgetH* handle, int maxw, int maxh)/* overload */
{
	QtImports::QWidget_setMaximumSize2(handle, maxw, maxh);
}
#pragma option pop
}	/* namespace Qt */

Of course it would be best to automate the process somehow, to avoid this being a very tedious fixing process and to remove the likelihood of human error. Perl seems the natural language for proceeding with this, but since I don't know Perl I opted to write some (quite unpleasant) code in Delphi to do the job.

The result is a new Qt.hpp header file you can find by clicking here.

Proof Of The Pudding

They say the proof of the metaphorical pudding is in the eating so let's sit down for our hors d'oeuvres (I'm mixing my metaphors here, but never mind). Examples to show the original problem with Qt on the Borland newsgroups involve simply calling the QApplication call's static exit method or regular quit method to terminate the application. This is done thus using the corresponding flat methods:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  QApplication_exit(0);
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
  QApplication_quit(Application->Handle);
}

The regular method (QApplication_quit) must have a QApplicationH pointer passed in to act as the this parameter in the method. The static method does not need this, as static methods are not bound to an instance of a class and so don't have a this pointer.

With the shipping product both of these calls result in a stack overflow.

With the fixed Qt header file they do not. It must be said that they don't actually terminate the application either, but that's because a VisualCLX application is not a normal Qt application. The CLX Application object takes charge of controlling the event processing and will only terminate when its Terminated property is True. This occurs if you call Application->Terminate() or the corresponding Qt code that gets called by the Terminate method.

QApplication_sendEvent(
  Application->Handle, QCustomEvent_create(QEventType_CMQuit, NULL));

The code sends a custom event to itself, which is picked up in the Application object's private EventFilter method, sets Terminated to True and then calls QApplication_quit.

More sample code to show it all works follows. The first event handler selects the entire contents of a multi-selection listbox (one that has the MultiSelect property set to True), whilst the second creates and shows a button.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  if (!ListBox1->MultiSelect)
    ListBox1->MultiSelect = true;
  QListBox_selectAll((QListBoxH*)ListBox1->Handle, true);
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
  QPushButtonH *Btn = QPushButton_create(Handle, "MyButton");
  QWidget_setGeometry(Btn, 5, 5, 85, 25);
  WideString Caption = "Hello";
  QButton_setText(Btn, PWideString(&Caption));
  QWidget_show(Btn);
}

It should be reiterated that using the Qt routines (the CLXDisplay API) when VisualCLX offers methods to do the same job is ill-advised. However sometimes you need to dip into the vast amount of additional routines when CLX doesn't cater for something. The examples shown here are simple cases to show that things are working correctly, rather than representations of realistic code snippets.

The Small Problem

Along the way to concocting this replacement Qt.hpp file I bumped into an anomaly that I hadn't been previously aware of. It arises due to the names chosen to expose the Qt class constructors within the Qt interface library. Delphi constructors are by convention named Create so all the flat constructors have the form QtClass_create, as in QPushButton_create and QComboBox_create.

The problem occurs because the QImage class both has a number of constructors and also offers an actual (overloaded) method called create. So the Qt interface library exposes seven routines for the constructors:

function QImage_create(): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create';
function QImage_create(width: Integer; height: Integer; depth: Integer;
  numColors: Integer; bitOrder: QImageEndian): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create2';
function QImage_create(p1: PSize; depth: Integer; numColors: Integer;
  bitOrder: QImageEndian): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create3';
function QImage_create(fileName: PWideString; format: PAnsiChar): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create4';
function QImage_create(data: QByteArrayH): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create6';
function QImage_create(data: PByte; w: Integer; h: Integer; depth: Integer;
  colortable: QRgbH; numColors: Integer; bitOrder: QImageEndian): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create7';
function QImage_create(p1: QImageH): QImageH;
  external QtShareName name QtNamePrefix + 'QImage_create9';

Unfortunately the Delphi declarations for the create method look like this:

function QImage_create(handle: QImageH; width: Integer; height: Integer;
  depth: Integer; numColors: Integer; bitOrder: QImageEndian): Boolean;
  external QtShareName name QtNamePrefix + 'QImage_create';
function QImage_create(handle: QImageH; p1: PSize; depth: Integer;
  numColors: Integer; bitOrder: QImageEndian): Boolean;
  external QtShareName name QtNamePrefix + 'QImage_create2';

See the problem? They're mapped onto DLL routines which are in fact flat constructors. So any call to these methods will actually construct a new QImage (with various bogus constructor parameters) and then effectively lose it, creating an immediate memory leak. There appears to be no exports from the Qt interface library that correctly surface these two methods so the external declarations and inline wrappers have been removed from the fixed C++ header file.

Parting Comments

You should bear in mind that the C++Builder product places certain restrictions on the usage of the CLXDisplay API. In the deploy.rtf and deploy.txt files you can see this clause:

2.6 Restrictions on CLXDisplay API (Qt.pas) Usage
CLXDisplay API, the Qt.pas interface to the Qt runtime, is only licensed for use in VisualCLX applications or a component that derives from TControl in the QControls unit. A VisualCLX application is an application that uses the TApplication object and uses at least one component derived from TControl. You are not licensed to use Qt.pas to create applications or components that exclusively call the Qt.pas interfaces. A separate commercial development license from Trolltech is required for use of Qt.pas in any manner other than authorized above.

So you are forbidden from building a pure Qt application using the CLXDisplay API without additional licensing. But as long as you access Qt routines from a VisualCLX application things are fine.

One final thought I will share is that I find it more than a bit concerning that the product was released by Borland with such a showstopper of a problem. Qt programming is simply impossible with C++Builder 6 without an awful lot of changes made to Qt.hpp. This smacks of a lack of QA on the product, which is always a disturbing revelation.

Further Reading/References

  1. How CLX Uses Qt, Brian Long, The Delphi Magazine , June 2001 (Issue 70).
  2. Programming Kylix with the CLXDisplay API , Bruno Sonnino.
  3. Problems Printing from C++Builder 6 CLX Applications, Brian Long.
    This article looks at a problem preventing CLX applications created in C++Builder 6 from being able to use the printer, and how to overcome it.
  4. Problems with Programming Qt from Kylix 3 C++ CLX Applications, Brian Long.
    This article looks at a problem preventing Kylix 3 C++ programmers from writing code that accesses the Qt library (the CLXDisplay API in the Qt.hpp header file) in a cross-platform friendly fashion.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has been providing training and consultancy on Borland's RAD products ever since, and is now moving into the .NET world.

Besides authoring a Borland Pascal problem-solving book published in 1994, Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi 2000 award and was voted Best Speaker at Borland's BorCon 2002 conference in Anaheim, California by the conference delegates.

There are a growing number of conference papers and articles available on Brian's Web site, so feel free to have a browse.

In his spare time (and waiting for his C++ programs to compile) Brian has learnt the art of juggling and making inflatable origami paper frogs.


Server Response from: ETNASC03