Wednesday, September 26, 2007

DN4DP#19: .NET only: Unsafe code

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we looked at a new array syntax supported in .NET. This post covers the dangerous sounding concept of unsafe code.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Unsafe code

Delphi for .NET now supports unsafe code. Since unsafe code fails PEVerify checks, you first have to enable the {$UNSAFECODE ON} compiler directive, then you have to mark the method with the unsafe directive.

{$UNSAFECODE ON}
function Foo(const A: array of char): integer; unsafe;
var
P: PChar;
Fixed: GCHandle;
begin
Fixed := GCHandle.Alloc(A, GCHandleType.Pinned);
try
P := Pointer(Fixed.AddrOfPinnedObject);
Result := 0;
while P^ <> #0 do
begin
Result := Result + Ord(P^);
Inc(P);
end;
finally
Fixed.&Free;
end;
end;
procedure Test;
var
I: integer;
begin
I := Foo(New(array[] of char, ('A', 'B', 'C')));
Writeln(I);
end;


Tip: Delphi for .NET does not currently have a fixed keyword to pin managed objects in memory. Use GCHandle.Alloc from the System.Runtime.InteropServices namespace instead. "

Monday, September 24, 2007

DN4DP#18: .NET only: New array syntax

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we looked at multi-unit namespace support. Now we will jump to a new array syntax supported in .NET.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"New array syntax

While native Delphi supports both static and dynamic arrays, Delphi for .NET now also supports multi-dimensional, rectangular dynamic arrays. These differ from jagged array of arrays in that there is only a single, continuous block of memory allocated for the items in it, and the size of all dimensions can be set dynamically at runtime. This is mostly a performance and memory usage optimization, but it is also required to be able to interface with external code that uses them.

The syntax to declare a multi-dimensional dynamic array is array[,] with one comma for each extra dimension. To allocate a new array, use the New(array [dim1, dim2 ..] of TElement) syntax. To change the size of an existing array, use SetLength with one or more dimension parameters – this will preserve the contents of the array.

var
MyArray: array of integer;
JaggedArray: array of array of integer;
MyMatrix: array[,] of integer;
MyCube: array[,,] of integer;
begin
MyArray := New(array [4] of integer);
JaggedArray := New(array [3] of array of integer);
MyMatrix := New(array [3,3] of integer);
MyCube := New(array [2,2,2] of integer);
//...
SetLength(MyMatrix, 10, 20);
SetLength(MyCube, 10, 20, 30);
end;


Note: While it is possible to create new arrays using SetLength, the New syntax generates slightly smaller and more efficient code. And SetLength cannot currently (Delphi 2006) be used to create a new multi-dimensional [,] array.


There are two new ways to create a new initialized dynamic array from a list of elements. You can use the New statement and follow the array type with a parantesed list of elements. Or you can use a new TArrayType.Create constructor syntax with the elements as parameters – this syntax is also supported in Win32.

begin 
MyArray := New(array[] of integer, (1, 2, 3));
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
// ...
MyArray := TIntegerArray.Create(1, 2, 3);
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
end;

This way of initializing dynamic arrays inline is a great improvement of the old way of first allocating the array using SetLength and then explicitly setting the value of each indexed element. This is particularly useful when calling one of the many FCL methods that have array parameters.

unit NewArraySyntaxU;

interface

procedure Test;

implementation

type
TStaticIntegerArray = array[0..5] of integer;
TIntegerArray = array of integer;
TJaggedArray = array of TIntegerArray;
// New in D2005, .NET only
{$IFDEF CLR}
TIntegerMatrix = array[,] of integer;
TIntegerCube= array[,,] of integer;
{$ENDIF}

{$IFDEF CLR}
procedure InnerDumpArray(const Prefix: string; A: System.Array);
var
O: TObject;
begin
write(Prefix);
for O in A do
begin
if O is System.Array
then InnerDumpArray(#13#10' ', System.Array(O))
else Write(O, ', ');
end;
end;

procedure DumpArray(const Name: string; A: System.Array);
begin
write(Name, ' (Rank=', A.Rank, ')' );
InnerDumpArray(': ', A);
Writeln;
end;
{$ELSE}
procedure DumpArray(const Name: string; const A: array of integer); overload;
var
I: Integer;
begin
write(Name, ': ');
for I in A do
Write(I, ', ');
Writeln;
end;

procedure DumpArray(const Name: string; const A: TJaggedArray); overload;
var
I: Integer;
IA : TIntegerArray;
begin
writeln(Name, ': ');
for IA in A do
begin
write(' ');
for I in IA do
Write(I, ', ');
Writeln;
end;
end;
{$ENDIF}

procedure Test;
var
MyStatics: TStaticIntegerArray;
MyArray: TIntegerArray;
JaggedArray: TJaggedArray;
I: integer;
{$IFDEF CLR}
MyMatrix: TIntegerMatrix;
MyCube: TIntegerCube;
{$ENDIF}
begin
// In .NET static arrays are implicitly allocated by the compiler at runtime
// and all elements are initially cleared (0)
// In Win32, static arrays are stored on the stack and contain random values
MyStatics[1] := 42;
DumpArray('MyStatics', MyStatics);

{$IFDEF CLR}
// Use New syntax to create a new array
MyArray := New(array [4] of integer);
MyArray[0] := 13;
DumpArray('MyArray1', MyArray);

// To New up a truly jagged array, new up each dimension separately
JaggedArray := New(array [3] of array of integer);
for I := Low(JaggedArray) to High(JaggedArray) do
JaggedArray[I] := New(array [I+1] of integer);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray1', JaggedArray);

// Use New(Type, dim1, dim2) to create a rectangular jagged array (array of arrays)
JaggedArray := New(TJaggedArray, 3, 2);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 1] := 3;
DumpArray('JaggedArray2', JaggedArray);

// New also supports initializing the array elements
MyArray := New(array[] of integer, (1, 2, 3));
DumpArray('MyArray2', MyArray);
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
DumpArray('JaggedArray3', JaggedArray);
{$ENDIF}

// Use SetLength to change the size of an existing array
SetLength(MyArray, 1);
DumpArray('MyArray3', MyArray);

// You can allocate a rectangualar "jagged" array using a single SetLength call
SetLength(JaggedArray, 3, 3);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray4', JaggedArray);

// To allocate a truly jagged array, use SetLength inside a loop
SetLength(JaggedArray, 3);
for I := 0 to 2 do
SetLength(JaggedArray[I], I+1);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray5', JaggedArray);

// Finally you can use the TArray.Create syntax to initialize an array with elements
MyArray := TIntegerArray.Create(1, 2, 3); // New in Win32 since D7
DumpArray('MyArray4', MyArray);
{$IFDEF CLR}
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
DumpArray('JaggedArray6', JaggedArray);

// You can also New up rectangular multidim [,] arrays
MyMatrix := New(array [3,3] of integer);
MyMatrix[2,2] := 19;
DumpArray('MyMatrix1', MyMatrix);

MyCube := New(array [2,2,2] of integer);
MyCube[1,0,1] := 9;
DumpArray('MyCube1', MyCube);

// Note: for [,] arrays you cannot used the type alias in the New statement
// MyMatrix := New(TIntegerMatrix, ((1,2,3), (4,5,6)));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
DumpArray('MyMatrix2', MyMatrix);
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
DumpArray('MyCube2', MyCube);

// Note that SetLength does not work correctly for *uninitialized* [,] arrays in 2006
// SetLength does work for initialized [,] arrays
SetLength(MyMatrix, 1, 2);
DumpArray('MyMatrix3', MyMatrix);
SetLength(MyCube, 1, 2, 3);
DumpArray('MyCube3', MyCube);
{$ENDIF}

end;

end.

"

Saturday, September 22, 2007

DN4DP#17: .NET only: Multi-unit namespaces

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered platform differences for floating-point semantics. This time we'll look at namespace support.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Multi-unit namespaces

With the trinity of a logical unit concept, physical unit source .pas files and compiled .dcu files, Delphi has always had a very efficient and useful module concept. To address the hierarchal namespace support required in .NET while still being backwards compatible, Delphi 8 introduced the concept of dotted unit names, such as Borland.Vcl.SysUtils and Borland.Vcl.Classes - these unit names were mapped directly to .NET namespaces.

This was a step in the right direction, but Delphi 2005 extended this concept to allow multiple Delphi units to contribute to the same logical namespace. Now the namespace of a dotted unit name is everything up to the last dot.

So now both the SysUtils and Classes units reside in a single Borland.Vcl namespace. This allows the programmer to split his code into multiple physical units, while exposing the contained classes in a single logical namespace. This makes it easier and more convenient to write assemblies that can be used by other languages (such as C#).

unit MultiUnit.Namespaces.Unit2;
...
class procedure TBar2.Foo;
begin
Writeln(TBar2.ClassInfo.ToString, '.Foo');
end;

The code above writes the fully qualified namespace of the TBar2 type, and the output is MultiUnit.Namespaces.TBar2.Foo in this case.

unit MultiUnit.Namespaces.Unit1;

interface

type
TBar1 = class
class procedure Foo; static;
end;

procedure Test;

implementation

class procedure TBar1.Foo;
begin
{$IFDEF CLR}
Writeln(TBar1.ClassInfo.ToString, '.Foo');
{$ELSE}
Writeln(TBar1.ClassName, '.Foo');
{$ENDIF}
end;

procedure Test;
begin
TBar1.Foo;
end;

end.
unit MultiUnit.Namespaces.Unit2;

interface

type
TBar2 = class
class procedure Foo; static;
end;

implementation

class procedure TBar2.Foo;
begin
Writeln(TBar2.ClassInfo.ToString, '.Foo');
end;

end.
unit MultiUnit.Namespaces.Unit3;

interface

procedure Test;

implementation

uses
MultiUnit.Namespaces.Unit1,
MultiUnit.Namespaces.Unit2;

procedure Test;
begin
Writeln('Note that even though TBar1 and TBar2 are in separate Delphi units');
Writeln('they reside in the same namespace (MultiUnit.Namespaces)');
TBar1.Foo;
TBar2.Foo;
end;

end.

"

Friday, September 21, 2007

DN4DP#16: .NET only: Floating-point semantics


This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered support for .NET Attributes. This time we will look at platform differences when it comes to floating-point operations.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Floating-point semantics

Native Delphi signals invalid floating-point operations by raising exceptions such as EZeroDivide and EOverflow. These semantics are preserved in Delphi for .NET by default. While the Win32 floating-point exception support is efficiently implemented directly in the processor hardware, the corresponding .NET support must be implemented explicitly in software by emitting the ckfinite IL instruction.

The .NET solution is a little slower, so if you have time-critical code that do not depend on exceptions being raised, you can speed it up a little by using the {$FINITEFLOAT OFF} compiler directive. In this mode, invalid operations will return special floating-point values like NaN (Not a Number), +Inf and -Inf (Infinity) instead of raising exceptions. To get the same semantics in Win32 code, you use the SetExceptionMask function from the Math unit.

{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised

In .NET the generated IL code for the division statement is:

FloatingPointSemanticsU.pas.51: Three := Two / One; 
IL_0063: ldloc.1
IL_0064: ldloc.0
IL_0065: div
IL_0066: ckfinite
IL_0067: stloc.2

At run time the ckfinite IL instruction actually expands into seven x86 instructions, including CALLing a routine – doing this for every floating-point operation slows things down noticeably.


Tip: Unless you absolutely need floating-point exceptions, turn them off with {$FINITEFLOAT OFF}

unit FloatingPointSemanticsU;

interface

procedure Test;

implementation

uses
SysUtils, Math;

procedure TestNaNs;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions off:');
{$IFDEF CLR}
{$FINITEFLOAT OFF}
{$ELSE}
Math.SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide,
exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
Three := Two / One; // Returns +Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := -42;
Three := Two / One; // Returns -Inf, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
One := 0;
Two := 0;
Three := Two / One; // Returns NaN, no exception raised
Writeln(Two:1:1 , ' / ', One:1:1, ' = ', Three:1:1);
end;

procedure TestExceptions;
var
One, Two, Three: Double;
begin
Writeln('Floating point exceptions on :');
{$IFDEF CLR}
{$FINITEFLOAT ON}
{$ELSE}
Math.SetExceptionMask([exDenormalized, exOverflow, exUnderflow, exPrecision]);
{$ENDIF}
One := 0;
Two := 42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := -42;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
One := 0;
Two := 0;
try
Write(Two:1:1 , ' / ', One:1:1, ' = ');
Three := Two / One;
Writeln(Three:1:1);
except
on E:EMathError do
writeln(E.ClassName, ': ', E.Message);
end;
end;

procedure Test;
begin
TestNaNs;
writeln;
TestExceptions;
end;

end.

"

Thursday, September 20, 2007

DN4DP#15: .NET only: Attributes support

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered support for .NET Boxing operations. We continue with the .NET specific topic of Attributes support.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Attributes support

Any proper .NET language must support associating attributes to classes and members, and Delphi is no exception. The attribute syntax closely resembles C#. The attribute class name (with or without the Attribute suffix) is placed inside square brackets prior to the element you want to apply it to. The attribute name may be followed by parens with constructor parameters and property assignments. Normally, the compiler will figure out what element the attribute belongs to by its context, but you can prefix the attribute name with a target indicator and colon, such as assembly:.

type
[MyCustom(Age=42, Name='Frank Borland')]
TMyObject = class

[MyCustomAttribute('Ida Emilie', 10)]
procedure Foo;
end;

[assembly:MyCustomAttribute('Thea Ulrikke', 3)]

Sample code

unit AttributesSupportU;

interface

type
[AttributeUsage(AttributeTargets.All)]
MyCustomAttribute = class(Attribute)
public
Name: string;
Age: Integer;
constructor Create; overload;
constructor Create(AName: string); overload;
constructor Create(AName: string; AAge: integer); overload;
function ToString: string; override;
end;
type
[MyCustom(Age=42, Name='Frank Borland')]
TMyObject = class
[MyCustomAttribute('Ida Emilie', 10)]
procedure Foo;
end;

[assembly:MyCustomAttribute('Thea Ulrikke', 3)]
procedure Test;

implementation

uses
SysUtils,
System.Reflection;

{ TMyCustomAttribute }

constructor MyCustomAttribute.Create;
begin
Create('', 0);
end;

constructor MyCustomAttribute.Create(AName: string);
begin
Create(AName, 0);
end;

constructor MyCustomAttribute.Create(AName: string; AAge: integer);
begin
inherited Create;
Name := AName;
Age := AAge;
end;

function MyCustomAttribute.ToString: string;
begin
Result := Format('%s, Name=%s, Age=%d', [inherited ToString, Name, Age]);
end;

{ TMyObject }

procedure TMyObject.Foo;
begin
Writeln('TMyObject.Foo');
end;

procedure Test;
var
T: System.Type;
O: TObject;
M: MemberInfo;
A: Assembly;
begin
T := TMyObject.ClassInfo;
Writeln(T.ToString);
for O in T.GetCustomAttributes(False) do
writeln(' ', O.ToString);
Writeln;
M := T.GetMethod('Foo');
Writeln(M.ToString);
for O in M.GetCustomAttributes(False) do
writeln(' ', O.ToString);
Writeln;
A := T.Assembly;
Writeln(A.ToString);
for O in A.GetCustomAttributes(False) do
writeln(' ', O.ToString);
end;

end.

"

Tuesday, September 18, 2007

DN4DP#14: .NET platform support: Boxing

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

The previous post covered overloaded default array properties. The next few posts will be .NET specific - starting out with support for Boxing operations.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

".NET platform support

Custom features for a managed environment

Most of the language extensions we have discussed so far have been general in nature and applicable to both the .NET and Win32 platforms. In addition, Borland [now CodeGear - Ed.] has added new language features that map directly to intrinsics of the .NET platform. These make it easier for you to integrate with the FCL, external assemblies, the CLR and so on.

Boxing

As you know from Chapter 2 ["Just In Time Compilation" - Ed.], boxing is the process of converting a value type instance into a System.Object compatible reference, copying the value to the garbage-collected heap. By default in Delphi, boxing is an explicit operation involving a cast expression from the value type to System.Object (or the alias TObject).

While explicit boxing helps you identify spots where potentially expensive copy operations are going on, it can become bothersome in the long run. To enable implicit boxing operations (just like in C#), you can use the {$AUTOBOX ON} compiler directive.

Finally, the syntax to perform unboxing is identical with C# - just cast the TObject reference to the required value type. You can use the is operator to verify the actual value type held by the object reference.

procedure Test;
var
Collection: ArrayList;
O: TObject;
begin
Collection := ArrayList.Create;

{$AUTOBOX OFF}
Collection.Add(TObject(42));

{$AUTOBOX ON}
Collection.Add(84);

for O in Collection do
writeln(Byte(O));
end;


Note: When boxing integral constants, the compiler will box them as the smallest integral type that can hold it. If you want to box a constant as a specific type, you need to cast it or store it in a correctly typed variable first, like this Collection.Add(Integer(84)).

Monday, September 17, 2007

DN4DP#13: Overloaded default array properties

It's been a while now, but this post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

What a good time to continue this series just after the major Delphi for .NET 2.0 launch, CodeGear RAD Studio 2007!

The previous post covered the exotic topic of Record Helpers. Today we'll look at another exotic-ish topic - overloaded default array properties.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Overloaded default array properties

A class or component can have an array property that is declared as default. This mechanism has now been extended to allow multiple overloaded default array properties - as long as the number or types of indexer parameters are different.

type
TMyObject = class
public
property Items[Index: integer]: string read GetItems write SetItems; default;
property Items[const Name: string]: string read GetNamedItems write SetNamedItems; default;
end;

This means that you can use the array indexing syntax on the object instance - effectively overloading the array subscript operator [].

procedure Test; 
var
MyObject: TMyObject;
begin
MyObject := TMyObject.Create;
MyObject[42] := 'The Answer';
MyObject['Bar'] := 'Yes';
end;

"



Copyright © 2004-2007 by Hallvard Vassbotn