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
- How CLX Uses Qt, Brian Long, The
Delphi Magazine , June 2001 (Issue 70).
- Programming
Kylix with the CLXDisplay API , Bruno Sonnino.
- 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.
- 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.
Connect with Us