Friday, October 05, 2007

DN4DP#23: .NET only: Obsolete features

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

The previous post showed how to use the mysterious P/Invoke features. This time we'll list the Win32 specific features of the language and RTL that didn't make it to the .NET side.

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.

"Obsolete features

Due to the managed and garbage collected nature of the .NET environment, a number of native Win32 specific features had to be left out in the Delphi for .NET language. Most of these are already warned against when you compile with the Win32 compiler, easing the porting process.

Feature    Comment
Pointers  Including PChar, @ operator, GetMem, etc. See Unsafe code
absolute    Variable overlaying not supported
Real48   This is a relic from the Borland Pascal (BP) days
File of <type> Size of records are not fixed in .NET
BlockRead, BlockWrite Size of records are not fixed in .NET
Old-style objects  Really a relic from BP – deprecated since Delphi 1
BASM   Built-in Assembler – is specific to x86 and native code
IUknown   No longer has AddRef, Release and QueryInterface
implements   Interface delegation, not implemented yet
automated, dispid  OLE Automation not supported

"

Tuesday, October 02, 2007

My article on CodeGear RAD Studio 2007

As some of you may know, I've recently written an article/review on the new CodeGear RAD Studio 2007. It was originally written in Norwegian (my first technical article in Norwegian - that was hard:)) and published on the Hardware Business site hwb.no:

http://www.hwb.no/test/utvikling/codegear_rad_studio_2007_/43487

Due to popular demand, I've translated the article into English and sent it to Nick Hodges so it might appear on CDN in the near future... ;)

Update: The translated article is available on CDN now:
http://dn.codegear.com/article/37131

Monday, October 01, 2007

DN4DP#22: .NET only: P/Invoke magic

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

Last time we're explored an undocumented corner of the language. This time we'll explore the exotic (and cryptic?) world of P/Invoke.

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.

"P/Invoke magic

Moving into the slightly more esoteric topics, Delphi for .NET supports two Platform Invoke (or P/Invoke) technologies called Reverse P/Invoke (or Unmanaged Exports) and Dynamic P/Invoke (or Virtual Library Interfaces).

Reverse P/Invoke lets you write a .NET DLL that can be used like any other DLL from Win32 code. It is a quick way of introducing .NET functionality into a Win32 application without performing a complete porting process or hosting the CLR explicitly.

Unmanaged exports must reside within a library project and generate thunks of unmanaged code, so you must turn {$UNSAFECODE ON}. The syntax is the same exports declaration as is used in Win32 Delphi. Only global-level routines can be exported, not class methods.

library ReversePInvoke;
procedure Foo(const S: string);
function Bar: integer;
function Greeting(Name: string): string;
//...
{$UNSAFECODE ON}
exports
Foo,
Bar,
Greeting;

On the Win32 side you import these routines just like you would import any other DLL, using external declarations.

const
LibName = 'ReversePInvoke.DLL';

procedure Foo(const S: string); stdcall; external LibName;
function Bar: integer; stdcall; external LibName;
function Greeting(Name: string): PChar; stdcall; external LibName;


Caution: Not all managed types can be used as parameters in exported routines. Generally you can use simple types and strings. String input parameters map to Win32 AnsiString, string results and output parameters map to PChar.


Virtual Library Interfaces uses Dynamic P/Invoke to import a Win32 DLL by using an interface to specify what routines to import. The DLL can be seen as a singleton object that implements the interface. The advantage is that you can use the Supports function from the Borland.Delphi.Win32 unit to check if the DLL and all the methods are available.

uses
Win32;
type
IMyInterface = interface
procedure Foo(const S: string);
function Bar: integer;
function Greeting(const Name: string): string;
end;

procedure Test;
var
MyInterface: IMyInterface;
begin
if Supports('Win32NativeDLL.DLL', TypeOf(IMyInterface), MyInterface) then
begin
Writeln('.NET App dynamically calling into Win32 DLL');
Writeln('The Answer is ', MyInterface.Bar);
MyInterface.Foo('.NET client');
Writeln(MyInterface.Greeting('Ida'));
end
else
Writeln('Cannot find Win32NativeDLL.DLL!');
end;

In effect you are dynamically loading the DLL if and only if it is available. If not, the application can continue running, but with reduced functionality. It also allows the application to control the folder the DLL is loaded from.


Tip: Use the LibraryInterface attribute to control calling convention and the wideness of string parameters. The defaults are CharSet.Auto (PChar on Win9x and PWideChar on WinNT) and CallingConvention.Winapi (or stdcall)."

Sunday, September 30, 2007

DN4DP#21: .NET only: Undocumented corner

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

Last time we're covered the differenced between classic single-cast events vs .NET-style multi-cast events. This time we'll dive into an undocumented corner of the language.

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.

"Undocumented corner

We have already mentioned the new semi-undocumented record helper feature in Delphi for .NET. Another undocumented and more subtle extension[1] is the ability to initialize global variables and typed constants with simple casting and constructor calls using constant parameters.

type
TFoo = class
constructor Create(A, B, C: integer);
end;
TBar = record
public
class operator Explicit(Value: Integer): TBar;
class operator Implicit(Value: Double): TBar;
end;

var
Foo: TFoo = TFoo.Create(1, 2, 3);
Bar1: TBar = TBar(42);
const
Bar2: TBar = 3.14;

In native Delphi you can initialize global variables with constant expressions such as integers, floating point values and strings. In .NET this has been extended to allow initialization of object references and records using a constructor call or an implicit or explicit cast operator. This feature can’t be used for instance fields or class vars, only for global variables and typed constants, so its usefulness is a little limited. The InitializeGlobals project demonstrates this new syntax.


Note: While this is currently an undocumented feature, it is fairly safe to assume it will continue to be available in the future. For instance, the Currency type in the Borland.Delphi.System unit is implemented as a record with operator overloading and it has implicit conversion operators from Double and Integer. Without this feature, there would be no way to initialize a global Currency variable (breaking existing Win32 code).





[1] This existence of this new syntax was first published by Chee Whee Chua (Borland Singapore) at http://blogs.borland.com/chewy/archive/2005/11/23/22210.aspx "



Update:  The new URL is: http://blogs.codegear.com/chewy/2005/11/23/22210

Friday, September 28, 2007

DN4DP#20: .NET only: Multi-cast events

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

Last time we're scared by unsafe code. Here we discuss the syntax for normal Delphi events vs .NET-style multicast events.

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-cast events

Delphi for .NET has full support for both old-style single cast events and .NET style multi cast events.

To write a traditional Delphi event, you first declare a delegate type using the procedure of object syntax, then declare an event property that reference a delegate field in the read and write specifiers.

type
TLevelChangedEvent = procedure (Sender: TObject; NewLevel: integer) of object;
TMyComponent = class
strict private
FOnLevelChanged: TLevelChangedEvent;
public
property OnLevelChanged: TLevelChangedEvent read FOnLevelChanged write FOnLevelChanged;
end;

This is a single-cast event that will compile both in .NET and Win32. It supports direct assignment of a method reference or nil to the event property and it supports directly invoking the event property. For instance, most VCL events are single cast and support assignments like this:

  MyComponent.OnLevelChanged := MyTest.FirstTarget;
MyComponent.OnLevelChanged(nil, 1);

However, many .NET consumers will expect multicast events in your classes. To enable this in a Delphi for .NET class you simply use add and remove specifiers instead of read and write, like this:

  TMyComponent = class
strict private
FOnMultiChanged: TLevelChangedEvent;
public
property OnMultiChanged: TLevelChangedEvent add FOnMultiChanged remove FOnMultiChanged;
end;

The simplest solution is simply to reference a delegate field as before - the compiler will then implement proper add_Event and remove_Event methods for you. In some special cases you may want to implement your own logic in these routines - to do that you simply write and reference your own add_Event and remove_Event methods, like this

  TMyComponent = class
strict private
FOnCustomChanged: TLevelChangedEvent;
public
procedure add_OnCustomChanged(Value: TLevelChangedEvent);
procedure remove_OnCustomChanged(Value: TLevelChangedEvent);
property OnCustomChanged: TLevelChangedEvent add add_OnCustomChanged remove remove_OnCustomChanged;
end;

procedure TMyComponent.add_OnCustomChanged(Value: TLevelChangedEvent);
var
Inlist: Delegate;
begin
if Assigned(FOnCustomChanged) then
for Inlist in Delegate(@FOnCustomChanged).GetInvocationList do
if InList.Equals(Delegate(@Value)) then
Exit;
FOnCustomChanged := TLevelChangedEvent(Delegate.Combine(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

procedure TMyComponent.remove_OnCustomChanged(Value: TLevelChangedEvent);
begin
FOnCustomChanged := TLevelChangedEvent(Delegate.Remove(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

This example add-handler only allows unique delegate targets, ignoring any attempt to add the same object’s method more than once. Note the tricky-looking code with casts to Delegate and use of the @-operator. The Delegate casts are required to force the compiler to treat the procedure of object as a System.Delegate instance (which is an implementation detail from the compiler’s point of view). The @-operator is required to prevent the compiler from trying to call the event instead of evaluating its value.

Most WinForms events are multicast events - they support multiple methods as targets. To add or remove a method from a multi-cast event, you use the Include and Exclude intrinsic procedures:

  Include(MyComponent.OnMultiChanged, MyTest.FirstTarget);
Include(MyComponent.OnMultiChanged, MyTest.SecondTarget);
MyComponent.TriggerMulti(6);
Exclude(MyComponent.OnMultiChanged, MyTest.FirstTarget);

This corresponds directly to the += and -= operators that C# supports on events.


Tip: Use multi-cast add/remove events for WinForms code and components. Use single-cast read/write events for VCL for .NET code and components, unless you really need multi-cast behavior. "


Update/Note: If you need to implement an interface that includes a multi-cast event property - i.e. the interface has add_Event and remove_Event methods - you can get by by declaring an add/remove event property on the class, with the add and remove specifiers referencing a procedure of object field. This will force the compiler to generate the add_Event and remove_Event methods for you - just like it did in the third code block above.

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.

"



Copyright © 2004-2007 by Hallvard Vassbotn