While reading the documentation at Anonymous Methods in Delphi I started to wonder. I've always used something like this:
type TMathFn = Function(A, B: Integer): Integer;
var fn: TMathFn;
Always worked for me. But this document tells me to use this instead:
type TMathFn = Reference to Function(A, B: Integer): Integer;
var fn: TMathFn;
And as I've been developing in Delphi from 1994 until 2010 I'm a bit unfamiliar with this "Reference to" part. Still, both options seem to work identically. So...
Are they identical?
"REFERENCE TO" is to allow Anonymous Methods (inline definitions of PROCEDUREs/FUNCTIONs), which can Capture a context (for example local variables, which are captured as references, ie. if you change the variable after capture, it's the modified value that is captured, see below).
TYPE TMyProc = REFERENCE TO PROCEDURE(CONST S : STRING);
PROCEDURE Information(CONST S : STRING);
BEGIN
MessageDlg(S,mtInformation,[mbOK],0)
END;
PROCEDURE RunProc(P : TMyProc ; CONST S : STRING);
BEGIN
P(S)
END;
PROCEDURE A(B,C : INTEGER);
VAR
D : INTEGER;
P : TMyProc;
BEGIN
D:=3;
// D is 3 at the time of capture
P:=PROCEDURE(CONST S : STRING)
BEGIN
Information(S+': '+IntToStr(D)+' -> '+IntToStr(B))
END;
// D is now 4 - and is reflected in the captured routine, as
// the capture is done by REFERENCE and not by VALUE.
INC(D);
RunProc(P,'Hello')
END;
BEGIN
A(2,3)
END.
Will show "Hello: 4 -> 2" in a message box.
The above definition of P "captures" (includes) the variables D and B so that even if you pass it to another function, where these variables don't exist, you can still access them.
This would be (nearly) impossible to do with ordinary PROCEDURE [OF OBJECT] types, as they can't access local variables declared at the point of execution.
No, they are not identical.
The difference is that
TMathFn = function(A, B: Integer): Integer;
is an ordinary function,
TMathMethod = function(A, B: Integer): Integer of object;
is a method, and
TMathAnonMethod = reference to function(A, B: Integer): Integer;
is an anonymous method, but you can also assign an ordinary function or a method to a variable of this type.
So, for instance, if
type
TMathFn = function(A, B: Integer): Integer;
TMathMethod = function(A, B: Integer): Integer of object;
TMathAnonMethod = reference to function(A, B: Integer): Integer;
function Test(A, B: Integer): Integer;
begin
Result := A + B;
end;
type
TTestClass = class
function Test(A, B: Integer): Integer;
end;
{ TTestClass }
function TTestClass.Test(A, B: Integer): Integer;
begin
Result := A + B;
end;
then the following applies:
procedure TForm1.FormCreate(Sender: TObject);
var
T: TTestClass;
F: TMathFn;
M: TMathMethod;
AM: TMathAnonMethod;
begin
T := TTestClass.Create;
try
F := Test; // compiles
F := T.Test; // doesn't compile
F := function(A, B: Integer): Integer
begin
Result := A + B;
end; // doesn't compile
M := Test; // doesn't compile
M := T.Test; // compiles
M := function(A, B: Integer): Integer
begin
Result := A + B;
end; // doesn't compile
AM := Test; // compiles
AM := T.Test; // compiles
AM := function(A, B: Integer): Integer
begin
Result := A + B;
end; // compiles
finally
T.Free;
end;
end;
Under the hood, as you probably already know, F is simply a (function) pointer and M is a method pointer. Anonymous methods, on the other hand, have a more involved interface-based implementation, which allows all their magic (like variable capture).
Related
Let suppose I have dynamic array
type TCharArr = Array of byte;
type PcharArr = ^TCharArr;
var charArr: PcharArr;
Which I want to allocate memory in Heap in the way of New(charArr);
However, how can I specify size and indexes? Is it possible dynamic array to have indexes eg. from 512.. to 1024?
Assuming a more recent Delphi version, you can mimic that with a generic record:
type
TDynArray<T> = record
private
FData: TArray<T>;
FOffset: Integer;
function GetData(Index: Integer): T;
function GetHigh: Integer;
function GetLength: Integer;
function GetLow: Integer;
procedure SetData(Index: Integer; const Value: T);
public
constructor Create(ALow, AHigh: Integer);
property Data[Index: Integer]: T read GetData write SetData; default;
property High: Integer read GetHigh;
property Length: Integer read GetLength;
property Low: Integer read GetLow;
end;
constructor TDynArray<T>.Create(ALow, AHigh: Integer);
begin
FOffset := ALow;
SetLength(FData, AHigh - ALow + 1);
end;
function TDynArray<T>.GetData(Index: Integer): T;
begin
Result := FData[Index - FOffset];
end;
function TDynArray<T>.GetHigh: Integer;
begin
Result := FOffset + System.High(FData);
end;
function TDynArray<T>.GetLength: Integer;
begin
Result := System.Length(FData);
end;
function TDynArray<T>.GetLow: Integer;
begin
Result := FOffset;
end;
procedure TDynArray<T>.SetData(Index: Integer; const Value: T);
begin
FData[Index - FOffset] := Value;
end;
The usage could look then like this:
var
arr: TDynArray<Integer>;
I: Integer;
begin
arr := TDynArray<Integer>.Create(512, 1024);
for I := arr.Low to arr.High do
arr[I] := I;
for I := arr.Low to arr.High do
Writeln(I, '=', arr[I]);
Readln;
end;
Dynamic arrays are always zero based. If you want to use array indices with a different base, then you would need to encapsulate the array access accounting for the offset to the indices. Something like this:
const
Offset = 512;
function GetValue(Index: Integer): Byte;
begin
Result := Arr[Index - Offset];
end;
procedure SetValue(Index: Integer; Value: Byte);
begin
Arr[Index - Offset] := Value;
end;
In addition there is the concept of a sparse array (sparse matrix). Delphi does not support it out of the box, but there were implementations in TurboPower SysTools, if I remember correctly.
The source was put on SourceForge, when the company closed about 15 years ago:
https://sourceforge.net/projects/tpsystools/
But these have not been updated for a looooong time.
This also seems to be the same library, maybe a bit more up to date:
https://github.com/TurboPack/SysTools
How can I use varargs to print out multiple strings? I tried this but I cannot determine the size of the array. It just prints garbage.
program Project1;
{$APPTYPE CONSOLE}
{$POINTERMATH ON}
function _Print(const S: String): string; cdecl;
var
Args: Array[0..100] of Pointer absolute S;
I: Integer;
begin
I := 0;
while Args[I] <> nil do
begin
WriteLn(PString(#Args[I])^);
Inc(I);
end;
end;
const Print: function(const S: String): string; cdecl varargs = _Print;
var
A, B: String;
begin
A := 'ABC';
B := 'CDE';
Print(a, b, 'asdasd', 'fasd', ' ')
end.
A varargs function has no automated way to determine the number of arguments being passed, because only the caller knows how many parameters it is putting on the call stack. The function must determine the arguments manually, either by:
requiring the caller to pass the actual number of parameter as a fixed parameter:
function _Print(NumStrings: Integer; const Strings: string): string; cdecl;
var
Args: Array[0..100] of Pointer absolute Strings;
I: Integer;
begin
for I := 0 to NumStrings-1 do
begin
WriteLn(PString(#Strings[I])^);
end;
end;
const
Print: function(NumStrings: Integer; const Strings: string): string; cdecl varargs = _Print;
var
A, B: String;
begin
A := 'ABC';
B := 'CDE';
Print(5, a, b, 'asdasd', 'fasd', ' ');
end.
putting a sentry value at the end of the parameter list that the function can then look for. Your function is already coded for this (it is looking for a nil pointer), so just pass one:
Print(a, b, 'asdasd', 'fasd', ' ', nil);
That being said, either approach is subject to caller error and thus potentially dangerous if misused, which is why varargs-style functions are not used very often. You should consider using an open-array parameter instead:
program Project1;
{$APPTYPE CONSOLE}
{$POINTERMATH ON}
function _Print(const Args: array of string): string;
var
I: Integer;
begin
for I := Low(Args) to High(Args) do
begin
WriteLn(Args[I]);
end;
end;
const
Print: function(const Args: array of string): string = _Print;
var
A, B: String;
begin
A := 'ABC';
B := 'CDE';
Print([a, b, 'asdasd', 'fasd', ' ']);
end.
When I use TObjectDictionary, where TKey is object, my application work uncorrectly.
I have two units, thats contain two classes. First unit:
unit RubTerm;
interface
type
TRubTerm = Class(TObject)
private
FRubricName: String;
FTermName: String;
public
property RubricName: String read FRubricName;
property TermName: String read FTermName;
constructor Create(ARubricName, ATermName: String);
end;
implementation
constructor TRubTerm.Create(ARubricName, ATermName: String);
begin
Self.FRubricName := ARubricName;
Self.FTermName := ATermName;
end;
end;
And second unit:
unit ClassificationMatrix;
interface
uses
System.Generics.Collections, System.Generics.Defaults, System.SysUtils, RubTerm;
type
TClassificationMatrix = class(TObject)
private
FTable: TObjectDictionary<TRubTerm, Integer>;
public
constructor Create;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
end;
implementation
constructor TClassificationMatrix.Create;
begin
FTable := TObjectDictionary<TRubTerm, Integer>.Create;
end;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.Add(ARubTerm, ADocsCount);
end;
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.TryGetValue(ARubTerm, Result);
end;
end;
But this fragment of code work unnormal:
procedure TestTClassificationMatrix.TestGetCount;
var
DocsCountTest: Integer;
begin
FClassificationMatrix.AddCount(10, 'R', 'T');
DocsCountTest := FClassificationMatrix.GetCount('R', 'T');
end;
// DocsCountTest = 0! Why not 10? Where is problem?
Thanks!
The fundamental issue here is that the default equality comparer for your type does not behave the way you want it to. You want equality to mean value equality, but the default comparison gives reference equality.
The very fact that you are hoping for value equality is a strong indication that you should be using a value type rather than a reference type. And that's the first change that I would suggest.
type
TRubTerm = record
RubricName: string;
TermName: string;
class function New(const RubricName, TermName: string): TRubTerm; static;
class operator Equal(const A, B: TRubTerm): Boolean;
class operator NotEqual(const A, B: TRubTerm): Boolean;
end;
class function TRubTerm.New(const RubricName, TermName: string): TRubTerm;
begin
Result.RubricName := RubricName;
Result.TermName := TermName;
end;
class operator TRubTerm.Equal(const A, B: TRubTerm): Boolean;
begin
Result := (A.RubricName=B.RubricName) and (A.TermName=B.TermName);
end;
class operator TRubTerm.NotEqual(const A, B: TRubTerm): Boolean;
begin
Result := not (A=B);
end;
I've added TRubTerm.New as a helper method to make it easy to initialize new instances of the record. And for convenience, you may also find it useful to overload the equality and inequality operators, as I have done above.
Once you switch to a value type, then you would also change the dictionary to match. Use TDictionary<TRubTerm, Integer> instead of TObjectDictionary<TRubTerm, Integer>. Switching to a value type will also have the benefit of fixing all the memory leaks in your existing code. Your existing code creates objects but never destroys them.
This gets you part way home, but you still need to define an equality comparer for your dictionary. The default comparer for a record will be based on reference equality since strings, despite behaving as value types, are stored as references.
To make a suitable equality comparer you need to implement the following comparison functions, where T is replaced by TRubTerm:
TEqualityComparison<T> = reference to function(const Left, Right: T): Boolean;
THasher<T> = reference to function(const Value: T): Integer;
I'd implement these as static class methods of the record.
type
TRubTerm = record
RubricName: string;
TermName: string;
class function New(const RubricName, TermName: string): TRubTerm; static;
class function EqualityComparison(const Left,
Right: TRubTerm): Boolean; static;
class function Hasher(const Value: TRubTerm): Integer; static;
class operator Equal(const A, B: TRubTerm): Boolean;
class operator NotEqual(const A, B: TRubTerm): Boolean;
end;
Implementing EqualityComparison is easy enough:
class function TRubTerm.EqualityComparison(const Left, Right: TRubTerm): Boolean;
begin
Result := Left=Right;
end;
But the hasher requires a little more thought. You need to hash each field individually and then combine the hashes. For reference:
Quick and Simple Hash Code Combinations
What is the canonical way to write a hasher function for TEqualityComparer.Construct?
The code looks like this:
{$IFOPT Q+}
{$DEFINE OverflowChecksEnabled}
{$Q-}
{$ENDIF}
function CombinedHash(const Values: array of Integer): Integer;
var
Value: Integer;
begin
Result := 17;
for Value in Values do begin
Result := Result*37 + Value;
end;
end;
{$IFDEF OverflowChecksEnabled}
{$Q+}
{$ENDIF}
function GetHashCodeString(const Value: string): Integer;
begin
Result := BobJenkinsHash(PChar(Value)^, SizeOf(Char) * Length(Value), 0);
end;
class function TRubTerm.Hasher(const Value: TRubTerm): Integer;
begin
Result := CombinedHash([GetHashCodeString(Value.RubricName),
GetHashCodeString(Value.TermName)]);
end;
Finally, when you instantiate your dictionary, you need to provide an IEqualityComparison<TRubTerm>. Instantiate your dictionary like this:
Dict := TDictionary<TRubTerm,Integer>.Create(
TEqualityComparer<TRubTerm>.Construct(
TRubTerm.EqualityComparison,
TRubTerm.Hasher
)
);
A Dictionary depends on a key value. You are storing a reference to an object in the key. If you create two objects that are setup identically the have different values and hence different keys.
var
ARubTerm1: TRubTerm;
ARubTerm2: TRubTerm;
begin
ARubTerm1 := TRubTerm.Create('1', '1');
ARubTerm2 := TRubTerm.Create('1', '1');
// ARubTerm1 = ARubTerm2 is not possible here as ARubTerm1 points to a different address than ARubTerm2
end;
Instead you could uses a String as the First Type Parameter in the TObjectDictonary that is based on RubricName and TermName. With this you would then get back the same value.
It should also be noted, that above code in XE2 creates two memory leaks. Every object created must be freed. Hence this section of code also is leaking memory
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.TryGetValue(ARubTerm, Result);
end;
Given all of that. If you want to use an Object as a Key you can do it with a Custom Equality Comparer. Here is your example changed to implement IEqualityComparer<T>, and fix a few memory leaks.
unit ClassificationMatrix;
interface
uses
Generics.Collections, Generics.Defaults, SysUtils, RubTerm;
type
TClassificationMatrix = class(TObject)
private
FTable: TObjectDictionary<TRubTerm, Integer>;
public
constructor Create;
procedure AddCount(ADocsCount: Integer; ARubName, ATermName: String);
function GetCount(ARubName, ATermName: String): Integer;
end;
implementation
constructor TClassificationMatrix.Create;
var
Comparer : IEqualityComparer<RubTerm.TRubTerm>;
begin
Comparer := TRubTermComparer.Create;
FTable := TObjectDictionary<TRubTerm, Integer>.Create([doOwnsKeys],TRubTermComparer.Create);
end;
procedure TClassificationMatrix.AddCount(ADocsCount: Integer; ARubName, ATermName: String);
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
FTable.Add(ARubTerm, ADocsCount);
end;
function TClassificationMatrix.GetCount(ARubName, ATermName: String): Integer;
var
ARubTerm: TRubTerm;
begin
ARubTerm := TRubTerm.Create(ARubName, ATermName);
try
if Not FTable.TryGetValue(ARubTerm, Result) then
result := 0;
finally
ARubTerm.Free;
end;
end;
end.
And the RubTerm.pas unit
unit RubTerm;
interface
uses Generics.Defaults;
type
TRubTerm = Class(TObject)
private
FRubricName: String;
FTermName: String;
public
property RubricName: String read FRubricName;
property TermName: String read FTermName;
constructor Create(ARubricName, ATermName: String);
function GetHashCode: Integer; override;
end;
TRubTermComparer = class(TInterfacedObject, IEqualityComparer<TRubTerm>)
public
function Equals(const Left, Right: TRubTerm): Boolean;
function GetHashCode(const Value: TRubTerm): Integer;
end;
implementation
constructor TRubTerm.Create(ARubricName, ATermName: String);
begin
Self.FRubricName := ARubricName;
Self.FTermName := ATermName;
end;
{ TRubTermComparer }
function TRubTermComparer.Equals(const Left, Right: TRubTerm): Boolean;
begin
result := (Left.RubricName = Right.RubricName) and (Left.TermName = Right.TermName);
end;
function TRubTermComparer.GetHashCode(const Value: TRubTerm): Integer;
begin
result := Value.GetHashCode;
end;
//The Hashing code was taken from David's Answer to make this a complete answer.
{$IFOPT Q+}
{$DEFINE OverflowChecksEnabled}
{$Q-}
{$ENDIF}
function CombinedHash(const Values: array of Integer): Integer;
var
Value: Integer;
begin
Result := 17;
for Value in Values do begin
Result := Result*37 + Value;
end;
end;
{$IFDEF OverflowChecksEnabled}
{$Q+}
{$ENDIF}
function GetHashCodeString(const Value: string): Integer;
begin
Result := BobJenkinsHash(PChar(Value)^, SizeOf(Char) * Length(Value), 0);
end;
function TRubTerm.GetHashCode: Integer;
begin
Result := CombinedHash([GetHashCodeString(Value.RubricName),
GetHashCodeString(Value.TermName)]);
end;
end.
In Delphi XE2, I'm trying to overload the in operator on a record to allow me to check whether the value represented by the record is part of a set. My code looks like this:
type
MyEnum = (value1, value2, value3);
MySet = set of MyEnum;
MyRecord = record
Value: MyEnum;
class operator In(const A: MyRecord; B: MySet): Boolean;
end;
class operator MyRecord.In(const A: MyRecord; B: MySet): Boolean;
begin
Result := A.Value in B;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MySet;
begin
R.Value := value1;
S := [value1, value2];
Button1.Caption := BoolToStr(R in S);
end;
The code fails to compile. For the statement R in S the compiler says: Incompatible types MyRecord and MyEnum.
How can I overload the In operator on MyRecord so that R in S will evaluate to True in the above code?
For the in operator to work the right operand must be of the record type since it's a set operator and not a binary operator. In your case it is the left operand.
So the following will work:
type
MyRecord = record
Value: MyEnum;
class operator In(const A: MyRecord; const B: MySet): Boolean;
end;
MyRecord2 = record
Value: MySet;
class operator In(const A: MyRecord; const B: MyRecord2): Boolean;
class operator In(const A: MyEnum; const B: MyRecord2): Boolean;
end;
class operator MyRecord.In(const A: MyRecord; const B: MySet): Boolean;
begin
Result := A.Value in B;
end;
class operator MyRecord2.In(const A: MyRecord; const B: MyRecord2): Boolean;
begin
Result := A.Value in B.Value;
end;
class operator MyRecord2.In(const A: MyEnum; const B: MyRecord2): Boolean;
begin
Result := A in B.Value;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
R2: MyRecord2;
begin
R.Value := value1;
R2.Value := [value1, value2];
if R in R2 then;
if value1 in R2 then;
end;
Well, you can almost do this, but you may not want to. AFAIK, class operators only work on the class (or record) they are defined within, so both R and S in your code have to be TMyRecord. With some injudicious use of implicit casting, we get the following:
unit Unit2;
interface
type
MyEnum = (value1, value2, value3);
MySet = set of MyEnum;
MyRecord = record
Value: MyEnum;
ValueSet: MySet;
class operator Implicit(A: MyEnum): MyRecord;
class operator Implicit(A: MySet): MyRecord;
class operator In (Left,Right:MyRecord): Boolean;
end;
implementation
class operator MyRecord.Implicit(A: MyEnum): MyRecord;
begin
Result.Value := A;
end;
class operator MyRecord.Implicit(A: MySet): MyRecord;
begin
Result.ValueSet := A;
end;
class operator MyRecord.In(Left, Right: MyRecord): Boolean;
begin
Result:= left.Value in Right.ValueSet;
end;
end.
The following will now complile, and even work:
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MyRecord;
begin
R.Value := value1;
S := [value1,value2,value3];
Button1.Caption := BoolToStr(R In S,true);
end;
Which, I'm sure we will all agree, is much more elegant than 'BoolToStr(R.Value in S)'.
However the following will also compile, but give the wrong result:
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MyRecord;
begin
R.Value := value1;
S := [value1,value2,value3];
Button1.Caption := BoolToStr(S In R,true);
end;
So, as Dorin commented, better to just have dull, staid old 'BoolToStr(R.Value in S)'. Unless of course you are being paid per line of code. And a bonus for bug-fixing.
The following code (constructed only to demonstrate the problem) compiles and works in Delphi 2010. In Delphi 2009, compiler fails with "E2035 Not enough actual parameters".
program Project50;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(param: integer);
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
end;
a := TProc(b); // <-- [DCC Error] Project50.dpr(19): E2035 Not enough actual parameters
end.
I have found only one very ugly hack to work around the problem (a: TProc absolute b). Does anybody knows of a nicer workaround for this compiler deficiency?
[TProc field is actually hidden inside a record that can store various 'executable' code - TProcedure, TMethod and TProc. Casting is used to store specific anonymous proc into this field.]
The trick is not to do
a := TProc(b);
but
TMyProc(a) := b;
That compiles and works in D2009. Sample project attached below.
program Project51;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(var param: integer);
TStorage = record
FDelegate: TProc;
end;
var
a : TMyProc;
b : TMyProc;
param: integer;
stg : TStorage;
begin
b := procedure (var param: integer)
begin
param := 2*param;
end;
// stg.FDelegate := TMyProc(b); // doesn't compile in Delphi 2009, compiles in Delphi 2010
TMyProc(stg.FDelegate) := b;
param := 21;
TMyProc(stg.FDelegate)(param);
Writeln(param);
Readln;
end.
However, this doesn't work if casting to a local variable.
var
p: TProc;
a: TMyProc;
TMyProc(p) := a; // this will not compile
Curiouser and curiouser.
I have found a hack #2:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(param: integer);
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
Writeln('asdf');
end;
PPointer(#a)^ := PPointer(#b)^;
a;
readln;
end.
I am in doubt what are you trying to achieve by assigning TMyProc (with param argument) to TProc (without argument)?
Updated: A hack #3 (should increment ref counter, the idea is stolen from System._IntfCopy):
procedure AnonCopy(var Dest; const Source);
var
P: Pointer;
begin
P:= Pointer(Dest);
if Pointer(Source) <> nil
then IInterface(Source)._AddRef;
Pointer(Dest):= Pointer(Source);
if P <> nil then
IInterface(P)._Release;
end;
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
Writeln('asdf');
end;
AnonCopy(a, b);
// PPointer(#a)^ := PPointer(#b)^;
a;
readln;
end.
It appears that the best way would be to use generics to store the correct type of delegate in the record. No hacks required.
program Project51;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(var param: integer);
TStorage<T> = record
FDelegate: T;
end;
var
a : TMyProc;
b : TMyProc;
p : TProc;
param: integer;
stg : TStorage<TMyProc>;
begin
b := procedure (var param: integer)
begin
param := 2*param;
end;
stg.FDelegate := b;
param := 21;
stg.FDelegate(param);
Writeln(param);
Readln;
end.