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.

"

No comments:



Copyright © 2004-2007 by Hallvard Vassbotn