Showing posts with label DN4DP. Show all posts
Showing posts with label DN4DP. Show all posts

Friday, November 02, 2007

DN4DP: The Delphi Language Chapter

We have finally come to an end in the long running series of of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

All the chapter excerpts that I have posted can be seen by clicking on the DN4DP blog label. As a service to our readers, I'm also including a full list of all the post links here.

Classic Delphi and .NET book in the making

Come get a free sample chapter!

.NET 2.0 for Delphi programmers available now

DN4DP#1: Getting classy

DN4DP#2: Protecting your privates

DN4DP#3: Nesting habits

DN4DP#4: Setting new records

DN4DP#5: Redefining the operators

DN4DP#6: Enumerating collections

DN4DP#7: Inlined routines

DN4DP#8: Unicode identifiers

DN4DP#9: Escaping keywords

DN4DP#10: With a little help from your friends

DN4DP#11: The try-finally-Free pattern

DN4DP#12: Record Helpers

DN4DP#13: Overloaded default array properties

DN4DP#14: .NET platform support: Boxing

DN4DP#15: .NET only: Attributes support

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

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

DN4DP#18: .NET only: New array syntax

DN4DP#19: .NET only: Unsafe code

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

DN4DP#21: .NET only: Undocumented corner

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

DN4DP#23: .NET only: Obsolete features

DN4DP#24: .NET vs Win32: Untyped parameters

DN4DP#25: .NET vs Win32: Casting

DN4DP#26: .NET vs Win32: Initialization and finalization

DN4DP#27: .NET vs Win32: Abstract classes

DN4DP#28: .NET vs Win32: Class references

DN4DP#29: .NET vs Win32: Constructors

DN4DP#30: Delphi vs C#

 

Hope you have enjoyed the series - and that you have bought (or will buy) the book! ;) Jon's book is stuffed with good stuff and is generally a much better read than "mine" chapter.


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.

Thursday, November 01, 2007

DN4DP#30: Delphi vs C#

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 .NET and Win32 constructors. This is the final post in this long running series and it covers the main differences between Delphi and C#.

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.

"Delphi vs C#

Very similar, but different in the details

If you are faced with the task for porting code from native Delphi to C#, or to convert C# code snippets to Delphi, or to make in informed decision of what language to use, it is useful to know the unique strengths and features of each language. In this section we quickly iterate over the highlights of each language and include some tips on porting between them.

Note that we compare Delphi for .NET to C# version 1.0, not version 2.0. It wouldn’t be fair to compare a .NET 1.1 product like Delphi for .NET 2006 to C# 2.0 with its support for generics, nullable types and iterators.

Delphi language highlights

Of the features that we have already covered, class helpers, unmanaged exports and virtual library interfaces are unique features that Delphi has. In one sense class helpers are very similar to the extension methods of the upcoming C# 3.0 standard to support the Linq (Language integrated query) technology. The main difference is that class helpers cannot help interfaces (at least not yet) and class helpers are more structured.

The following table lists the most important Delphi language features that C# does not have and what their alternative is when porting code.

Delphi feature Comment C# alternatives
class helpers Platform-leveling compiler magic

Explicit static methods C# 3.0 extension methods

Unmanaged exports a.k.a. reverse P/Invoke Use C++, hacks
Virtual Library Interfaces a.k.a. dynamic P/Invoke Use unmanaged C++, hacks
sets Limited to ordinal types with <= 256 elements

enum flags, BitArray,
int bit-fiddling

class of references Meta classes System.Type
virtual class methods Meta class polymorphism System.Type, reflection
virtual constructors Class factories Activator, reflection
Type-less var and out parameters Poor-man’s generics C# 2.0 Generics, System.Object function
type aliases, typed types Logical vs. actual types Explicit typing
Default parameters Simpler than overloading Overloading
resourcestrings Simplified internationalization Resources and ResourceManager.GetString
Named constructors Simulated using overloading Overloading
message methods Dispatching windows messages WndProc switch
Variants One-type fits all System.Object boxing
Global routines Non-OOP code Static methods of a class
Global variables Non-OOP data Static fields of a class
Named array properties Multiple array properties

Overloaded this indexer
Nested class with this indexer

Local (nested) procedures Implementation hiding, automatic access to outer variables private method, anonymous method (C# 2.0)
variant records (case) Structure overlaying (union)

[StructLayout(LayoutKind.Explicit)],
[FieldOffset()]

Text files, Writeln, etc Easy input/output Console and Stream classes
Supports Win32, Linux Cross-platform capabilities

Use C/C++,
Mono for Linux


One difference that is important to be aware of is that hard-casts and safe-casts have opposite syntax in Delphi and C#. The safe exception-raising cast is (O as TargetType) in Delphi and (TargetType)O in C#. The nil/null-returning cast is TargetType(O) in Delphi and (O as TargetType) in C#.

C# language highlights

The following C# 1.0 features are not directly available in Delphi, but have alternative ways of achieving the same goal.













































C# feature Comment Delphi alternatives
lock Thread synchronization
Monitor.Enter(O); 
try
// ..
finally
Monitor.Exit(O);
end;
fixedGarbage collection object pinning
H := GCHandle.Alloc(..) 
try
P := H.AddrOfPinnedObject;
finally
H.&Free;
end;
using Deterministic releasing of unmanaged resources
O := TMy.Create; 
try
// ..
finally
O.Free;
end;
C# destructor ~ClassNameGarbage collection deallocate notificationoverride Finalize method
stackallocUnsafe code temporary allocationsGCHandle.Alloc dynamic array

checked/
unchecked

Integer arithmetic overflow checking
{$OVERFLOWCHECKS ON/OFF}
{$Q+/-}
readonly fieldRead-only fields initialized in a constructorconst, read-only property, normal field
returnSet function result and return to caller
Result := O; 
Exit;
volatile fieldMay be modified outside current threadExplicit locks, Thread.VolatileRead/Write
internal accessPer-assembly cross-class implementation details

public,
protected with cracker-cast

ternary ? : operatorInline test and return resultif .. then .. else, IfThen routines
switch (string)Multi-case testing of stringsnested if..then..else, TStringList.IndexOf


In Delphi an overridden Destroy destructor maps to an implementation of IDisposable, while in C# you must implement IDisposable explicitly. In C# a ~ClassName destructor maps to an overridden Object.Finalize method, while in Delphi Finalize must be overridden manually. In most cases, application-level code needs to implement IDisposable, but should not override Finalize – that should be left to low-level leaf-classes in the FCL.

"

Wednesday, October 31, 2007

DN4DP#29: .NET vs Win32: Constructors

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 class references. This posts covers .NET vs Win32 constructors.

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.

"Constructors

While it is a good rule in Win32 to have all constructors call an inherited or peer constructor, the compiler does not enforce it. In .NET the runtime refuses to load types that break this rule. In addition you cannot access inherited fields or call any methods until you have called an inherited constructor.

type
TBar = class
protected
FInheritedField: integer;
end;
TFoo = class(TBar)
private
FField: integer;
procedure Method;
public
constructor Create;
end;

constructor TFoo.Create;
begin
FField := 42;
{$IFNDEF CLR}
Method;
FInheritedField := 13;
{$ENDIF}
inherited Create;
FInheritedField := 13;
Method;
end;

Note that unlike C#, in Delphi you can still modify the fields of the current instance before calling the inherited constructor."

Tuesday, October 30, 2007

DN4DP#28: .NET vs Win32: Class references

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 differences in abstract class behavior. Here we look at class references.

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.

"Class references 

For the most part, the semantics of using class of references to create late-bound types of classes is unchanged in .NET. The only noticeable difference is that in .NET the constructor you call through the class reference must be declared virtual. In Win32 it doesn’t strictly have to be, but normally it should be declared virtual."

Sunday, October 28, 2007

DN4DP#27: .NET vs Win32: Abstract classes

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 initialization and finalization sections. This post covers some minor differences in abstract class behavior.

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.

"Abstract classes

The concept of abstract classes is strictly enforced by the CLR runtime – it will not allow you to instantiate instances of abstract classes or classes containing abstract methods. In Win32 you can create instances of classes containing abstract methods. Normally you will get a compiler warning, though – this warning has been turned into a compiler error in .NET.

When creating instances through a class of reference, the compiler’s static checking cannot prevent you from compiling code that could potentially instantiate abstract classes. In Win32 this will go undetected at runtime, unless you actually call an abstract method – then you will get an EAbstractError exception. In .NET, the runtime will raise an exception if you try to call the constructor of an abstract class. The AbstractClasses project demonstrates these differences.

Code Sample

unit AbstractClassesU;

interface

type
TFoo = class
procedure Bar; virtual; abstract;
constructor Create; virtual;
constructor Create2;
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
procedure Bar; override;
constructor Create; override;
end;

procedure Test;

implementation

var
Foo: TFoo;
FooClass: TFooClass;

{ TFoo }

constructor TFoo.Create;
begin
inherited Create;
writeln('TFoo.Create');
end;

constructor TFoo.Create2;
begin
inherited Create;
writeln('TFoo.Create2');
end;

{ TBar }

constructor TBar.Create;
begin
inherited Create;
writeln('TBar.Create');
end;

procedure TBar.Bar;
begin
writeln('TBar.Bar');
end;

{$DEFINE TEST_ERRORS}
{$IFDEF TEST_ERRORS}
procedure TestErrors;
begin
// Direct creation of class with abstract method
Foo := TFoo.Create; // Win32 Warning / .NET error

// Call of abstract method
Foo.Bar; // Win32 run-time exception

// Calling non-virtual constructor through class reference
FooClass := TBar;
Foo := FooClass.Create2; // .NET compile-time error

// Creation of abstract class via class reference
FooClass := TFoo;
Foo := FooClass.Create; // .NET run-time error

// Call of abstract method
Foo.Bar; // Win32 run-time error
end;
{$ENDIF}

procedure TestOK;
begin
// Creation of concrete class via class reference
FooClass := TBar;
Foo := FooClass.Create;
Foo.Bar;
writeln('OK');
end;

procedure Test;
begin
TestOK;
{$IFDEF TEST_ERRORS}
TestErrors;
{$ENDIF}
end;

end.

"

Friday, October 26, 2007

DN4DP#26: .NET vs Win32: Initialization and finalization

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 the .NET and Win32 casting issues. Here we quickly covers some potential gotchas related to initialization and finalization sections.

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.

 "Initialization and finalization

On the native Win32 platform, the Delphi unit initialization and finalization sections have exact (static) execution order and execution time semantics. On the .NET platform, the Delphi compiler implements initialization sections in terms of class constructors – this means that the order of execution is dependent on what types are used in what order at runtime. Likewise, running of an assembly’s unit finalization sections is triggered by a global object’s Finalize method – this implies that the code runs at an unspecified time on the CLR’s finalizer thread (and it is not always guaranteed to occur).

While most simple initialization and finalization code should work as is, you should be careful with code that relies on the execution order of these sections, code that touches types from other units and code that closes physical resources such as files. Old code that only frees memory in the finalization section can typically be IFDEFed out in .NET code.

unit InitializatonAndFinalizationU;

interface

type
TFoo = class
constructor Create;
end;

procedure Test;

implementation

uses
InitializatonAndFinalizationU2;

procedure Test;
begin

end;

{ TFoo }

constructor TFoo.Create;
begin
inherited Create;
writeln('TFoo.Create');
end;

initialization
TBar.Create;
writeln('InitializatonAndFinalizationU.initialization');

finalization
writeln('InitializatonAndFinalizationU.finalization');

end.
unit InitializatonAndFinalizationU2;

interface

type
TBar = class
constructor Create;
end;

procedure Test;

implementation

uses
InitializatonAndFinalizationU;

procedure Test;
begin

end;

{ TBar }

constructor TBar.Create;
begin
inherited Create;
writeln('TBar.Create');
end;

initialization
TFoo.Create;
writeln('InitializatonAndFinalizationU2.initialization');

finalization
writeln('InitializatonAndFinalizationU2.finalization');

end.
program InitializatonAndFinalization;

{$APPTYPE CONSOLE}

uses
SysUtils,
InitializatonAndFinalizationU in 'InitializatonAndFinalizationU.pas',
InitializatonAndFinalizationU2 in 'InitializatonAndFinalizationU2.pas';

begin
InitializatonAndFinalizationU.Test;
readln;
end.

Saturday, October 20, 2007

DN4DP#25: .NET vs Win32: Casting

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 the .NET and Win32 differences for untyped var and out parameters. Here we look at casting issues.

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.

"Casting

There are also some casting differences[1] between the two platforms. In Win32, hard-casts are unsafe, because the compiler will not complain if you perform obviously illegal casts. Hard-casts is a way of telling the Win32 compiler; "Relax, I know what I'm doing. Just close your eyes and reinterpret these bits as the type I'm telling you it is". So there are no checks and no conversions going on – it is just a binary reinterpretation.

In .NET even hard-casts are safe, in the sense that the compiler and runtime will check that the cast is valid. The CLR will check that the source is compatible with the target type - if not, nil is returned instead. Conceptually, .NET hard-casts like this

  Target := TTargetClass(Source); 

work like this

  Target := TTargetClass(Source); 
if Source is TTargetClass then
Target := Source
else
Target := nil;

This means that a typical native Win32 pattern of

  if O is TMyObject then 
TMyObject(O).Foo;

has the same semantics in .NET, but a more slightly more efficient .NET-only alternative is

  MyObject := TMyObject(O); 
if Assigned(MyObject) then
MyObject.Foo;

As you know, casting between TObject and value types in .NET performs boxing and unboxing operations (see Chapter 5 for the details). In Win32 you can only cast value types of size 4 bytes or less to and from TObject – and the cast will only store the binary bits of the value type in the reference storage itself.

unit CastingDifferencesU;

interface

type
TMyObject = class
procedure Foo;
end;

procedure Test;

implementation

{ TMyObject }

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

procedure Test;
var
O: TObject;
{$IFDEF CLR}
MyObject: TMyObject;
{$ENDIF}
begin
// Both Win32 and .NET supports is-checks, as-casts and hard-casts
O := TMyObject.Create;
if O is TMyObject then
TMyObject(O).Foo;
{$IFDEF CLR}
// In .NET a failing hard-casts returns nil -
// in Win32 the result it undefined (you typically break the type system)
MyObject := TMyObject(O);
if Assigned(MyObject) then
MyObject.Foo;
{$ENDIF}
end;

end.




[1] For more details about how casting works in Win32 and .NET see my blog posts at http://hallvards.blogspot.com/"

Sunday, October 07, 2007

DN4DP#24: .NET vs Win32: Untyped parameters

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

The previous post listed the Win32 specific language and RTL features. The next few posts will focus on minor differences in implementation between Win32 and .NET - starting with differences in the detailed semantics of untyped var and out parameters.

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.

"Win32 and .NET differences

In addition to the new language features and the obsolete features we have already mentioned, there are some implementation details that are different between the Win32 and .NET versions of the Delphi language. These differences are minor, but it is useful to know about them.

Untyped var and out parameters

It is interesting to note that Delphi supports type-less var and out parameters in a strictly typed and managed environment like .NET.

var
GlobalInt: integer;

procedure FooVar(var Bar);
var
BarValue: integer;
begin
BarValue := Integer(Bar);
Inc(BarValue);
Bar := BarValue;
end;

procedure TestFooVar;
begin
GlobalInt := 1;
FooVar(GlobalInt);
end;

The implementation relies on boxing the actual argument to and from System.Object before and after the method call. The compiler compiles the code above like this

procedure FooVarImpl(var Bar: TObject);
var
BarValue: integer;
begin
BarValue := Integer(Bar); // Unbox
Inc(BarValue);
Bar := TObject(BarValue); // Autobox
end;

procedure TestFooVarImpl;
var
Temp: TObject;
begin
GlobalInt := 1;
Temp := TObject(GlobalInt); // Autobox
FooVarImpl(Temp);
GlobalInt := Integer(Temp); // Unbox - after the routine returns
end;

Because of this implementation, you will not see intermediate modifications of the actual argument until the call returns. The compiler allows direct assignments to the untyped parameter in .NET (as if $AUTOBOX is turned ON just for that TObject parameter) – this is not allowed in Win32. In addition, left-hand-side casts of an untyped parameter is not allowed in .NET – only in Win32. This can make it hard to write single-source routines using var and out parameters without resorting to IFDEFs. The UntypedParameters project demonstrates these differences.

unit UntypedParametersU;

interface

procedure Test;

implementation

{$DEFINE CHECK_SIDEFFECTS}

type
TMyObject = class
end;

var
GlobalInt: integer;
GlobalRef: TMyObject;

procedure FooVar(var Bar);
var
BarValue: integer;
begin
BarValue := Integer(Bar);
{$IFDEF CHECK_SIDEFFECTS}
writeln('1. FooVar Bar = ', BarValue);
writeln('1. FooVar GlobalInt = ', GlobalInt);
{$ENDIF}
Inc(BarValue);
{$IFDEF CIL}
Bar := BarValue;
{$ELSE}
Integer(Bar) := BarValue;
{$ENDIF}
{$IFDEF CHECK_SIDEFFECTS}
writeln('2. FooVar Bar = ', Integer(Bar));
writeln('2. FooVar GlobalInt = ', GlobalInt);
if GlobalInt = Integer(Bar)
then writeln('Win32 untyped var semantics')
else writeln('.NET untyped var semantics');
{$ENDIF}
end;

procedure FooVarImpl(var Bar: TObject);
var
BarValue: integer;
begin
BarValue := Integer(Bar);
Inc(BarValue);
Bar := TObject(BarValue);
end;

procedure FooOut(out Bar);
begin
{$IFDEF CIL}
Bar := Integer(42);
{$ELSE}
Integer(Bar) := 42;
{$ENDIF}
{$IFDEF CHECK_SIDEFFECTS}
writeln('1. FooOut Bar = ', Integer(Bar));
writeln('1. FooOut GlobalInt = ', GlobalInt);
if GlobalInt = Integer(Bar)
then writeln('Win32 untyped out semantics')
else writeln('.NET untyped out semantics');
{$ENDIF}
end;

procedure FooConst(const Bar);
var
Temp: integer;
begin
writeln('1. FooConst Bar = ', Integer(Bar));
writeln('1. FooConst GlobalInt = ', GlobalInt);
Temp := Integer(Bar);
writeln('1. FooConst Temp = ', Temp);
end;

procedure NilRef(var Obj);
begin
{$IFDEF CIL}
Obj := nil;
{$ELSE}
TObject(Obj) := nil;
{$ENDIF}
{$IFDEF CHECK_SIDEFFECTS}
if GlobalRef = nil
then writeln('Win32 untyped var semantics')
else writeln('.NET untyped var semantics');
{$ENDIF}
end;

procedure NilRefImpl(var Obj: TObject);
begin
Obj := nil;
{$IFDEF CHECK_SIDEFFECTS}
if GlobalRef = nil
then writeln('Win32 untyped var semantics')
else writeln('.NET untyped var semantics');
{$ENDIF}
end;

procedure TestNilRef;
begin
GlobalRef := TMyObject.Create;
NilRef(GlobalRef);
end;

procedure TestNilRefImpl;
var
Temp: TObject;
begin
GlobalRef := TMyObject.Create;
Temp := GlobalRef;
NilRefImpl(Temp);
GlobalRef := TMyObject(Temp);
end;

procedure TestFooVar;
begin
GlobalInt := 1;
FooVar(GlobalInt);
end;

procedure TestFooVarImpl;
var
Temp: TObject;
begin
GlobalInt := 1;
Temp := TObject(GlobalInt);
FooVarImpl(Temp);
GlobalInt := Integer(Temp);
end;

procedure Test;
begin
TestNilRef;
{$IFDEF CIL}
TestNilRefImpl;
{$ENDIF}
TestFooVar;
{$IFDEF CIL}
TestFooVarImpl;
{$ENDIF}
Writeln('2. GlobalInt = ', GlobalInt);
FooOut(GlobalInt);
Writeln('3. GlobalInt = ', GlobalInt);
FooConst(GlobalInt);
end;

end.

"

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

"

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.

"

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.

"



Copyright © 2004-2007 by Hallvard Vassbotn