Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
![]() |
![]() |
![]() |
|
Kylix & Delphi 6 New Language Features
Kylix and Delphi 6 come with a number of changes in the compiler and the RunTime Library (RTL).
In this short article, I'll show you a few of the nice new features that I've encountered in the few weeks that I have played with Delphi 6 (note that since these are Compiler and RTL features, they are present in every version of Delphi 6 - or Kylix for that matter).
Compiler Directives
There are a number of new compiler directives in Delphi 6.
Obviously, now that Kylix is available, we need to be aware that some code may have to be written twice: one time specifically for Linux and another time specifically for Windows.
On Linux, the compiler has defined LINUX, and on Windows we now have MSWINDOWS.
Note that Delphi 5 does not support MSWINDOWS yet, but rather uses WIN32.
Also note that you should never use LINUX in one clause, and decide that the alternative (ELSE) is Windows, since potentially, in time there could be other alternatives (CLX is cross-platform, and could potentially be modified to run on Solaris or MacOS X in a possible future version of Delphi).
The preferred way to use the new compiler directive is as follows:
{$IFDEF MSWINDOWS} // some Delphi 6 code {$ENDIF} {$IFDEF LINUX} // some Kylix code {$ENDIF}The $ALIGN compiler directive has new options to align fields inside records and classes: {$A1} for byte-alignment, {$A2} for word-alignment, {$A4} for double-word alignment, and {$A8} for quad-word alignment. For optimal speed, {$A8} should be used.
procedure TForm1.Button1Click(Sender: TObject); const TheAnswer = 42; begin {$IF Defined(MSWINDOWS) and Declared(TheAnswer) and (TheAnswer = 42)} ShowMessage('Hello, Windows World!'); {$ELSE} ... {$IFEND} end;Using the $IF statement, we can check the existence of conditional defines such as LINUX or MSWINDOWS (with the Defined function), as well as the existence of a Pascal constant definition (with the Declared function) and value itself (see above). Note that we have to end the $IF with $IFEND (instead of $ENDIF). We can also use $ELSE, $ELSEIF in combination with the $IF and $IFEND, but we have to remember that these new compiler directives are only supported by Kylix and Delphi 6 (Delphi 5 and lower only support the IFDEF/ELSE/ENDIF directives).
Variants
Delphi 5 has variants which are based on COM.
This was a problem for Kylix on Linux, so Borland decided to design a new kind of cross-platform variants.
The new variants were also moved from the System unit to the Variants unit.
In other words: if you use variants in any way, you need to add the Variants unit to your uses clause.
The new variants can be used with Delphi 6 as well as Kylix (so they are indeed cross-platform).
As an additional benefit of the new design, variants can now also contain Int64 values.
Custom Variants
Apart from the new cross-platform variants, Delphi 6 now allows us to define our own custom variant types.
In order to create our own variant type, we have to derive a new class from TCustomVariantType (or TInvokeableVariantType or TPublishableVariantType).
For more information and two good examples, Delphi 6 already contains full source code of some custom variant types, namely the VarCmplx unit (which contains the implementation of complex number variants) and the VarConv unit (which contains the implementation of variant types used by the unit conversion ConvUtils unit).
Enumerated Types
There are a few areas in which the C/C++ syntax is not only less readable but also - let's be honest - more powerful than Delphi's ObjectPascal syntax (one could argue that the latter often results in the former).
One of the features available in C/C++ is the ability to assign specific values to enumeration types.
This is not possible in Delphi 5.
But it is possible in Kylix and Delphi 6.
We can now write the following:
type QMessageBoxButton = (NoButton = 0, Ok = 1, Cancel = 2, Yes = 3, No = 4, Abort = 5, Retry = 6, Ignore = 7, ButtonMask = $07, Default = $100, Escape = $200, FlagMask = $300);Note that the value of ButtonMask is the same as Ignore, and that there are significant gaps between ButtonMask and Default, Escape and FlagMask.
Typed Constants
Earlier versions of Delphi introduced the so-called "initialised variables", where a variable declaration is immediately followed by a value to initialise the variable, for example:
var PerInitialisedVariable: Integer = 42;Although the syntax was new to us, the concept was not. In previous versions of Delphi we could already use something called "typed constants". A typed constant is a constant definition with a typename included (almost like an initialised variable), for example:
const
ConstConstant = 42; // cannot change value
TypedConstant: Integer = 7;
I still don't know if it originally was a bug or a feature, but it turned out that typed constants in Delphi can be changed as well (i.e. they are in fact not truly constant), so we can write the following line of code to change the value of the type constant TypedConstant to 42.
begin TypeConstant := 42 end.The fact that typed constants were not in fact constant had a significant effect on the use of these values. Real constant constants can be used as case labels, for example, but typed constants cannot. See the following code for example:
case SomeInteger of
ConstConstant: writeln('Yes');
// TypedConstant: writeln('Does Not Compile');
else writeln('No')
end;
Typed constants cannot be used as case labels since the compiler is unable to determine (at compile time) the value of the typed constant.
Apart from global typed constants, you could also define a typed constant inside a routine, resulting in a local typed constant.
Again, the typed constant can be modified inside this routine (and only inside the routine, since nobody outside the routine can see it).
But as a special side-effect of typed constants, it will be initialised only once, and at all other times retain its current value.
So if you start with a value of 1, and modify it inside the routine, then next time you enter the routine, the modified value will be there (waiting for you).
You can use this "feature" as a simple counter, to measure the amount of times a routine is called:
procedure Routine; const CounterOnlyVisibleInsideRoutine: Integer = 0; begin Inc(CounterOnlyVisibleInsideRoutine); end;The local typed constant "CounterOnlyVisibleInsideRoutine" can be seen as global variable with local scope (only inside the Routine) and full lifetime. I love them, because apart from counters, you can also use them to implement a simple "history" feature. And since their scope is local, you are ensured that nothing else can touch them or modify them. Safe and clean global variables. What else do you want? Needless to say, I used them a lot.
Enter Kylix and Delphi 6
For some reason, Delphi 6 and Kylix consider typed constants real constant.
You cannot change them!
In fact, statements that attempt to do so will result in a genuine compiler error!
Initially, this broke a lot of legacy code from me, and at first sight, it seemed I had no choice but to convert the clean local typed constant into a truly global (initialised) variable or global typed constant.
Resulting in code which is less clean and in my view a bit harder to maintain.
const CounterNowAlsoVisibleOutsideRoutine: Integer = 0; procedure Routine; begin Inc(CounterNowAlsoVisibleOutsideRoutine); end;Fortunately, it's possible to "fix" since you can compile with {$J+} to end up with the "old" kind of typed constants (although the on-line help advises you not do use them anymore).
Interface Properties
Delphi 6 now supports interface properties.
This means that we can have a property that can be assigned with any component that implements this interface.
For example, we could have a data-aware control with an interface property called data field, which is defined as IDataField.
The interface IDataField should be part of the TField definition (so all TField derived classes also have to implement it).
Once we've done that, we can assign any TField (which implements the IDataField interface) to the data field property of a data-aware control.
Interface properties (properties whose type is an interface) can be published if and only if the implementer of the property is a streamable component.
This means you can now see properties that take an interface in the Object Inspector, which will provide a drop-down list of components that support the interface.
I think this opens up all kinds of new possibilities to explore.
Needless to say, you'll be seeing more about interface properties in the future, so stay tuned...