Problems with Programming Qt from Kylix 3 C++ CLX Apps
Problems with Programming Qt from
Kylix 3 C++ CLX Applications
Brian Long (www.blong.com)
Table of Contents
Click
here to download the file associated with this article.
Introduction
A spate of issues have recently come to my attention regarding accessing a
certain portion of functionality from C++ applications. In particular, C++ CLX
programs that wish to access the underlying Qt API (referred to as the CLXDisplay
API) tend to encounter one of a number of problems. For C++Builder 6 programmers,
any attempt to access these routines leads immediately to a Stack Overflow (see
Problems with
Programming Qt from C++Builder 6 CLX Applications, which you can also
access through Reference 3 in the Further
Reading section at the end of this article). Once this hurdle has been crossed,
attempts to use the printer either through Qt or through the high-level TPrinter
class prove fruitless (see Reference 4).
Another problem relating to Qt routines is present in the C++ version of Kylix
3. If you plan to write cross-platform CLX applications, which occasionally
dip into the CLXDisplay API, you will come unstuck as Kylix 3 uses different
definitions for every one of the key data types (the Qt handle classes), forcing
you to write conditional code for even the smallest piece of Qt access code.
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.
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 interface 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 Problem
The problem is that the Qt.hpp header file and various additional headers that
it uses were manufactured incorrectly. The main Qt.hpp file was created by the
Linux Delphi command-line compiler (dcc), which can generate C++ header files
that correspond to the interface section of a Delphi unit being compiled. Inside
the unit are several thousand import declarations for routines exposed from
the Qt interface library.
As has been discussed at length in Reference 3, the
header generation functionality in the Delphi compiler is a bit shaky with import
declarations of the type used in this unit. The C++Builder 6 CLX issue is solely
due to this incorrect behaviour in the Delphi compiler, however with the Kylix
3 C++ product Borland tried to make things a bit better. And whilst you can
certainly use the Qt routines without getting a Stack Overflow, you can only
do so using symbol names that are inconsistent with those offered by C++Builder
6.
C++Builder 6 was released in February 2002 with a single Qt.hpp header containing
everything that was declared in the interface section of the Qt.pas unit. Kylix
3 followed along in July 2002 with a different scheme to avoid the same problems
as found in C++Builder 6 (not that any official fixes have been offered for
C++Builder 6 CLX programmers). Instead of leaving the entire job of generating
the whole Qt.hpp header file down to the dcc compiler, there are now a couple
of tools that do the job.
As a consequence, Qt.hpp now contains no declarations of routines available
in the Qt interface library. Instead, all the CLXDisplay API routines are spread
across a multitude of header files with .hb and .hhk file extensions. Each Qt
class that has methods has a corresponding .hb file (such as qprinter.hb). If
the Qt class offers hooks then it will also have a .hhk file (for example qwidget.hb
and qwidget.hhk).
Because of the content of all these various header files, C++ Qt program code
is now inconsistent between Win32 and Linux. For example, this simple statement
should invoke the printer setup dialog and works fine in C++Builder 6 (assuming
you have taken account of the required fixes described in Reference
3 and Reference 4):
#include <QPrinters.hpp>
...
QPrinter_setup((QPrinterH *)Printer()->Handle, NULL);
Of course you would generally never make such a call since the TPrinter
object returned by the Printer()
function offers an ExecuteSetup()
method that does this for you, but this statement serves as an example.
The function call corresponds directly to this Delphi code that will work in
Delphi 6 and 7 and also in the Delphi language version of Kylix (any object
reference type in Delphi is represented explicitly as an object pointer in C++):
uses
Qt, QPrinters;
...
QPrinter_setup(QPrinterH(Printer.Handle), nil);
However to get the same behaviour in Kylix 3 C++ you need to either use this
line:
#include <QPrinters.hpp>
...
QPrinter_setup((QPrinter__ *)Printer()->Handle, NULL);
or this line:
#include <QPrinters.hpp>
...
QPrinter_setup((::QPrinterH)Printer()->Handle, NULL);
As you can see the QPrinterH
handle class is no longer defined the same way. No longer is it a class type
in the Qt namespace, but it's actually defined as a pointer type in the global
namespace. However a new symbol called QPrinter__
is also available, which seems to have the same purpose as the QPrinterH
does in C++Builder. This is all very awkward, and requires code to look something
like this for cross-platform compatibility:
#include <QPrinters.hpp>
...
#ifdef _Windows
QPrinter_setup((QPrinterH *)Printer()->Handle, NULL);
#endif
#ifdef __linux__
QPrinter_setup((QPrinter__ *)Printer()->Handle, NULL);
#endif
or even like this:
#include <QPrinters.hpp>
...
#ifdef _Windows
QPrinter_setup((QPrinterH *)Printer()->Handle, NULL);
#endif
#ifdef __linux__
QPrinter_setup((::QPrinterH)Printer()->Handle, NULL);
#endif
Clearly this makes any amount of cross-platform Qt programming very tedious,
having to write everything twice, slightly different each time, so we will look
for a solution to the problem.
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 (as described in the Reference
3). This causes a big problem since the Qt.pas unit is made up of over 3300
import declarations. It seems that to avoid the problems caused by this bug,
the C++ Qt interface library functionality was made available slightly differently.
Every single import declaration in Qt.pas is followed by the Delphi $EXTERNALSYM
compiler directive to ensure that the dcc/dcc32.exe generated header contains
no trace of the corresponding C++ import declaration, however all the Qt handle
classes are generated in the Kylix 3 header just as they are in C++Builder 6,
within the Qt namespace.
Instead of letting the Delphi compiler add the import declarations into the
main Qt.hpp file some other utility runs through each of the real Qt header
files in order to produce the external declarations in separate header files.
Each of these headers is included by Qt.hpp thanks to the presence of additional
compiler directives in Qt.pas. Ahead of the import declarations for each Qt
class in Qt.pas is a $HPPEMIT
directive to generate a specific #include for a .hb file, and also sometimes
a .hhk file. The Qt.pas unit has sections like this:
{$HPPEMIT '#include
"qwidget.hb"'}
{$HPPEMIT '#include "qwidget.hhk"'}
function QWidget_create(parent: QWidgetH; name: PAnsiChar; f: WFlags): QWidgetH; cdecl;
{$EXTERNALSYM QWidget_create}
procedure QWidget_destroy(handle: QWidgetH); cdecl;
{$EXTERNALSYM QWidget_destroy}
function QWidget_winId(handle: QWidgetH): Cardinal; cdecl;
{$EXTERNALSYM QWidget_winId}
{$HPPEMIT '#include "qapplication.hb"'}
{$HPPEMIT '#include "qapplication.hhk"'}
function QApplication_argc(handle: QApplicationH): Integer; cdecl;
{$EXTERNALSYM QApplication_argc}
function QApplication_argv(handle: QApplicationH): PPAnsiChar; cdecl;
{$EXTERNALSYM QApplication_argv}
function QApplication_style(): QStyleH; cdecl;
{$EXTERNALSYM QApplication_style}
{$IFDEF MSWINDOWS}
function QApplication_winEventFilter(handle: QApplicationH; p1: PMsg): Boolean; cdecl;
{$ENDIF}
{$IFDEF MSWINDOWS}
{$EXTERNALSYM QApplication_winEventFilter}
{$ENDIF}
{$IFDEF LINUX}
function QApplication_x11EventFilter(handle: QApplicationH; p1: PEvent): Boolean; cdecl;
{$ENDIF}
{$IFDEF LINUX}
{$EXTERNALSYM QApplication_x11EventFilter}
{$ENDIF}
{$HPPEMIT '#include "qwidget.hb"'}
{$HPPEMIT '#include "qwidget.hhk"'}
function QWidget_hook_create(handle: QObjectH): QWidget_hookH; cdecl;
{$EXTERNALSYM QWidget_hook_create}
procedure QWidget_hook_destroy(handle: QWidget_hookH); cdecl;
{$EXTERNALSYM QWidget_hook_destroy}
{$HPPEMIT '#include "qapplication.hb"'}
{$HPPEMIT '#include "qapplication.hhk"'}
function QApplication_hook_create(handle: QObjectH): QApplication_hookH; cdecl;
{$EXTERNALSYM QApplication_hook_create}
procedure QApplication_hook_destroy(handle: QApplication_hookH); cdecl;
{$EXTERNALSYM QApplication_hook_destroy}
procedure QApplication_hook_hook_lastWindowClosed(handle: QApplication_hookH; hook: QHookH); cdecl;
{$EXTERNALSYM QApplication_hook_hook_lastWindowClosed}
Notice that for each Qt class the headers are included twice? The generated
Qt.hpp file does indeed include all these .hb/.hbk files twice unnecessarily.
The code that is actually placed into Qt.hpp during the header generation is
fine, as it stands, since effort has been made to ensure that it does not contain
invalid import declarations as happened in C++Builder 6. Instead it just contains
the various type definitions made use of by the Qt routines, all within the
Qt namespace. This is an example Qt handle class from Qt.hpp:
class DELPHICLASS QWidgetH;
class PASCALIMPLEMENTATION QWidgetH : public QObjectH
{
typedef QObjectH inherited;
public:
#pragma option push -w-inl
/* TObject.Create */ inline __fastcall QWidgetH(void) :
QObjectH() { }
#pragma option pop
#pragma option push -w-inl
/* TObject.Destroy */ inline __fastcall virtual
~QWidgetH(void) { }
#pragma option pop
};
So next we need to look at these .hb and .hhk files to locate the source of
the problem.
A .hb file is a Borland-generated header that declares import declarations
for a given Qt class's methods. For example the QTimer
class is surfaced with the file qtimer.hb:
#ifndef C_QTIMER_H
#define C_QTIMER_H
/****************************************************************************
** QTimer C bindings generated from reading C++ file 'qtimer.h'
**
** Created: Tue Jul 30 19:22:32 2002
** by: The Qt Meta Object Compiler ($Revision: 2.53 $)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
C_EXPORT QTimerH QTimer_create(QObjectH parent, const char* name);
C_EXPORT void QTimer_destroy(QTimerH handle);
C_EXPORT bool QTimer_isActive(QTimerH handle);
C_EXPORT int QTimer_start(QTimerH handle, int msec, bool sshot);
C_EXPORT void QTimer_changeInterval(QTimerH handle, int msec);
C_EXPORT void QTimer_stop(QTimerH handle);
C_EXPORT void QTimer_singleShot(int msec, QObjectH receiver, const char* member);
#endif
A .hhk file defines the hook routines used to install
hooks for a given Qt class. For example, the QTimer
hook routines are defined in qtimer.hhk:
#ifndef C_QTIMERK_H
#define C_QTIMERK_H
/****************************************************************************
** QTimer_hook C bindings generated from reading C++ file 'qtimer.hk'
**
** Created: Tue Jul 30 19:23:37 2002
** by: The Qt Meta Object Compiler ($Revision: 2.53 $)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
typedef struct QTimer_hook__ { int dummy; } *QTimer_hookH;
/****************************************************************************
** QTimer_hook C bindings generated from reading C++ file 'qtimer.hk'
**
** Created: Tue Jul 30 19:23:37 2002
** by: The Qt Meta Object Compiler ($Revision: 2.53 $)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
C_EXPORT QTimer_hookH QTimer_hook_create(QObjectH handle);
C_EXPORT void QTimer_hook_destroy(QTimer_hookH handle);
C_EXPORT void QTimer_hook_hook_timeout(QTimer_hookH handle, QHookH hook);
#endif
You need to remember that these headers are included in Qt.hpp before the generated
definitions of all the Qt handle classes. In fact any $HPPEMIT
directive causes the output to appear at the start of the header before any
of the translated items. Consequently all these routines will need definitions
of all the handle classes to work with, since that's what they mainly deal in.
To cater for this the qtypes.h header has a multitude of additional auto-generated
type declarations appended to it as shown below. qtypes.h is #included by Qt.hpp
thanks to another $HPPEMIT directive
in Qt.pas.
/****************************************************************************
** QWidget C bindings generated from reading C++ file 'qwidget.h'
**
** Created: Mon Dec 6 16:31:03 1999
** by: The Qt Meta Object Compiler (1.1)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
typedef struct QWidget__ { int dummy; } *QWidgetH;
/****************************************************************************
** QApplication C bindings generated from reading C++ file 'qapplication.h'
**
** Created: Mon Dec 6 16:30:45 1999
** by: The Qt Meta Object Compiler (1.1)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
typedef struct QApplication__ { int dummy; } *QApplicationH;
/****************************************************************************
** QTimer C bindings generated from reading C++ file 'qtimer.h'
**
** Created: Mon Dec 6 16:31:02 1999
** by: The Qt Meta Object Compiler (1.1)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
typedef struct QTimer__ { int dummy; } *QTimerH;
Additionally, the .hhk files contain definitions of the hook classes in a similar
fashion, as we can see above. You might notice these
seem to be dummy type declarations (which are not made in any specific namespace).
You might also notice that unlike the proper handle class types auto-generated
in Qt.hpp, these handle types are actually pointer types. For example, QWidgetH
has a class type definition in the Qt namespace (in Qt.hpp) but a pointer type
definition in the global namespace (in qtypes.h); a pointer that points to a
dummy QWidget__ struct. This
is inconsistent.
The consequence of these inconsistent dummy (placeholder) type declarations
is that all the import declarations are defined incorrectly (when compared with
the C++Builder 6 versions, or what you would expect by examining the Delphi
declarations).
For example, this import declaration from earlier:
C_EXPORT QTimerH QTimer_create(QObjectH parent, const char* name);
should in truth be defined like this to keep in line with the the way the Qt interface routines are exported and how the
auto-generated handle types are defined.
extern "C" QTimerH *QTimer_create(QObjectH *parent,
const char* name);
Because of the disparity of the extern
declarations, we have to write the code differently in each product.
On top of all of this is the fact that the Kylix 3 C++ product only surfaces
the real exported name of each of the Qt interface library routine. In the original
Qt classes there are many overloaded methods and constructors, for example the
QImage class offers 7 constructors.
However the Qt interface library exports standard routines that must be uniquely
named, so in fact the Qt interface routines to access these 7 constructors are
defined like this (taken from Qt.pas):
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';
Each one is exported with a slightly different name and this is the name you
must call from Kylix 3 C++. This is quite unlike C++Builder 6, which attempts
to provide overloaded C++ wrapper routines to turn these exports back into same-named
routines and remove the requirement to know which numbered routine takes which
parameters. This means that code is even less cross-platform friendly than it
could be. For example this code creates a button, displays it and updates it,
and is designed to be cross-platform inasmuch as how the shipping products require
it:
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
TRect r(5, 5, 85, 25);
WideString Caption = "Hello";
#ifdef _Windows
QPushButtonH *Btn = QPushButton_create(Handle, "MyButton");
QWidget_setGeometry(Btn, &r);
QButton_setText(Btn, PWideString(&Caption));
QWidget_show(Btn);
#endif
#ifdef __linux__
QPushButton__ *Btn = QPushButton_create((QWidget__ *)Handle, "MyButton");
QWidget_setGeometry2((QWidget__ *)Btn, &r);
QButton_setText((QButton__ *)Btn, PWideString(&Caption));
QWidget_show((QWidget__ *)Btn);
#endif
}
The Windows code calls QWidget_setGeometry
but the Linux code has to call QWidget_setGeometry2.
And with all the extra typecasts required in the Linux code, things are not
looking good. So even if we fix the Qt type definitions somehow we will still
be left with the job of creating overloaded wrappers for all the routines like
this (and there are over 600 of them).
Fixing The Problem
The most straightforward approach to rectifying this problem is to use the
original Qt.pas Delphi unit and generate an entire standalone Qt.hpp header
file in the same way I described in the Reference 3.
In order to do this we must remove all the $EXTERNALSYM
directives that are after every import declaration in the unit and also remove
the $HPPEMIT directives that
include the plethora of unhelpful custom headers as well as qtypes.h. That would
change a section like the one shown above to this:
function QWidget_create(parent: QWidgetH;
name: PAnsiChar; f: WFlags): QWidgetH; cdecl;
procedure QWidget_destroy(handle: QWidgetH); cdecl;
function QWidget_winId(handle: QWidgetH): Cardinal; cdecl;
function QApplication_argc(handle: QApplicationH): Integer; cdecl;
function QApplication_argv(handle: QApplicationH): PPAnsiChar; cdecl;
function QApplication_style(): QStyleH; cdecl;
{$IFDEF MSWINDOWS}
function QApplication_winEventFilter(handle: QApplicationH; p1: PMsg): Boolean; cdecl;
{$ENDIF}
{$IFDEF MSWINDOWS}
{$ENDIF}
{$IFDEF LINUX}
function QApplication_x11EventFilter(handle: QApplicationH; p1: PEvent): Boolean; cdecl;
{$ENDIF}
{$IFDEF LINUX}
{$ENDIF}
function QWidget_hook_create(handle: QObjectH): QWidget_hookH; cdecl;
procedure QWidget_hook_destroy(handle: QWidget_hookH); cdecl;
function QApplication_hook_create(handle: QObjectH): QApplication_hookH; cdecl;
procedure QApplication_hook_destroy(handle: QApplication_hookH); cdecl;
procedure QApplication_hook_hook_lastWindowClosed(handle: QApplication_hookH; hook: QHookH); cdecl;
In order to get all the Linux-specific declarations in the header we will need
to use the Kylix Delphi compiler, dcc, to generate the header file. Additionally,
since Kylix 3 and Delphi 7 alter the way they generate a header file for a Delphi
unit, Kylix 2 must be used in order to generate an appropriately formed header
file. Kylix 2's dcc will generate import declarations for all the routines,
as well as inline wrapper routines, which are overloaded as necessary to match
the original Pascal import declarations in the Qt.pas unit, however Kylix 3's
dcc will not. These inline wrappers will call down to the actual import declarations.
This isn't a straightforward process, as the generation of these import declarations
and inline wrappers has its own problems, but from there we can use the same
process of fixing as
was done for C++Builder 6 in Reference 3.
Having run this through the mill I can inform you that Qt.hpp exposes 3380
routines from the Qt interface library, of which about 640 are overloads. 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 tuck
in. Examples of some simple Qt programming follow and will now compile in both
C++Builder 6 as well as the C++ IDE in Kylix 3:
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.
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 underlying API behind Borland's cross-platform component library, CLX,
does not itself support cross-platform coding in the shipping Kylix 3 and C++Builder
6 products (at least not in any practical way). This is only the case with the
C++ products; the Win32 Delphi product and Delphi version of Kylix 3 successfully
support cross-platform coding.
This problem, as well the other ones I've recently unearthed (see References
3 & 4 in the next section), smacks of
a lack of QA on the products, 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
with Programming Qt from C++Builder 6 CLX Applications, Brian Long.
This article looks at a problem preventing C++Builder 6 CLX applications from
directly accessing the routines in Qt.hpp (the CLXDisplay API).
- 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.
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.