Mutually dependent procedural variable and record - delphi

I have the following construct:
program Project26;
{$APPTYPE CONSOLE}
{$R *.res}
type
TPrint_address_func = function(offset: integer; info: disassembler_info): boolean;
disassembler_info = record
data: string;
print_address_func: TPrint_address_func;
end;
begin
end.
Obvious either the record of the function-type needs to be declared in a forward declaration.
I know that I cannot declare the record as forward, but...
Is there a way to declare the procedural-variable as forward?
Or can I replace the record with an old-school object and declare that as forward?

You cannot forward declare procedural types, or records. So, the conclusion is that you have to put the type definition inside the record:
type
disassembler_info = record
type
TPrint_address_func = function(info: disassembler_info): boolean;
var
data: string;
print_address_func: TPrint_address_func;
end;
FWIW, once I start defining types inside records, I tend to start breaking the declaration up with visibility specifiers. I'd declare this type like this:
type
disassembler_info = record
public
type
TPrint_address_func = function(info: disassembler_info): boolean;
public
data: string;
print_address_func: TPrint_address_func;
end;

If you pass a record pointer, then this problem is easy to solve, even in Delphi versions that don't support nested record types. Forward-declare a record pointer type, and then declare the function type using the record pointer. Finally, declare the record:
type
PDisassembler_info = ^TDisassembler_info;
TPrint_address_func = function(offset: Integer;
info: PDisassembler_info): Boolean;
TDisassembler_info = record
data: string;
print_address_func: TPrint_address_func;
end;
You're probably going to have more than just one function pointer, and you're probably going to have more than one instance of your record, too. As you extend this pattern, you're ultimately going to re-invent classes. Consider this:
type
TDisassembler_info = class
data: string;
function print_address(offset: Integer): Boolean; virtual; abstract;
end;
Now, instead of defining a free function, you declare a descendant of your class and override the abstract method. This has a few advantages as the number of function pointers and record instances grows:
The compiler automatically fills in the function pointers with all the right values. It stores them in the class's VMT. There's no chance you'll have a null function pointer by accidentally forgetting to assign print_address_func. The compiler will warn if you attempt to instantiate a class without overriding the abstract methods.
It's impossible to accidentally pass the wrong record pointer when you call the function. In your design, calling the function will look like this:
info.print_address_func(offset, info);
It would surely be an error if the record parameter you passed differed from the record whose function you called. With an object, the redundancy and opportunity for error go away:
info.print_address(offset);
No matter how many functions you have, the size of a single instance of the class remains constant because all instances share a single VMT. In your current model, if you have 100 instances of your record, you'll have 100 copies of the same function pointer.

It is possible to solve this with a record helper.
Type
disassembler_info = record
private
FP: Pointer;
public
data: string;
end;
TPrint_address_func = function(info: disassembler_info): boolean;
disassembler_info_helper = record helper for disassembler_info
private
procedure SetAFunc(aF: TPrint_Address_Func);
function GetAFunc: TPrint_Address_Func;
public
property print_address_func: TPrint_address_func read GetAFunc write SetAFunc;
end;
function disassembler_info_helper.GetAFunc: TPrint_Address_Func;
begin
Result := TPrint_address_func(FP);
end;
procedure disassembler_info_helper.SetAFunc(aF: TPrint_Address_Func);
begin
TPrint_address_func(FP) := TPrint_address_func(aF);
end;
function MyFunc(aRec: disassembler_info): boolean;
begin
Result := true;
WriteLn('Hello from MyFunc');
end;
var
aFunc: TPrint_address_func;
aRec:disassembler_info;
begin
aRec.print_address_func := MyFunc;
aFunc := arec.print_address_func;
if aFunc(aRec) then begin
WriteLn('Voila!');
end;
ReadLn;
end.
The helper injects a property of TPrint_address_func with read and write methods that operates on a private variable declared in disassembler_info.

Related

How to pass generic procedure TProc<T1,T2> as a parameter and invoke it?

I have a logging class, which links to many modules. The main method of this class is a class method:
type
TSeverity = (seInfo, seWarning, seError);
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity);
end;
Somewhere else I have a function DoSomething() which does some things that I would like to log. However, I do not want to link all the modules of the logger to the module in which 'DoSomething()' is declared to use the logger. Instead I would like to pass an arbitrary logging method as a DoSomething's parameter and call it from its body.
The problem is that TLogger.Log requires parameter of TSeverity type which is defined in logger class. So I can't define a type:
type
TLogProcedure = procedure(AMessage: String; ASverity: TSeverity) of Object;
because I would have to include an unit in which TSeverity is declared.
I was trying to come up with some solution based on generic procedure but I am stuck.
uses
System.SysUtils;
type
TTest = class
public
class function DoSomething<T1, T2>(const ALogProcedure: TProc<T1,T2>): Boolean; overload;
end;
implementation
class function TTest.DoSomething<T1, T2>(const ALogProcedure: TProc<T1, T2>): Boolean;
var
LMessage: String;
LSeverity: Integer;
begin
//Pseudocode here I would like to invoke logging procedure here.
ALogProcedure(T1(LMessage), T2(LSeverity));
end;
Somewehere else in the code I would like to use DoSomething
begin
TTest.DoSomething<String, TSeverity>(Log);
end;
Thanks for help.
Update
Maybe I didn't make myself clear.
unit uDoer;
interface
type
TLogProcedure = procedure(AMessage: String; AErrorLevel: Integer) of Object;
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
// I thoight that I can somehow generalize this procedure using generics.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
Separate unit with one of the logging mechanisms.
unit uLogger;
interface
type
TSeverity = (seInfo, seWarning, seError);
// I know that I could solve my problem by introducing an overloaded method but I don't want to
// do it like this. I thought I can use generics somehow.
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity); {overload;}
{class procedure Log(AMessage: String; ASeverity: Integer); overload;}
end;
implementation
class procedure TLogger.Log(AMessage: String; ASeverity: TSeverity);
begin
//...logging here
end;
{class procedure TLogger.Log(AMessage: String; ASeverity: Integer);
begin
Log(AMessage, TSeverity(ASeverity));
end;}
end.
Sample usage of both units.
implementation
uses
uDoer, uLogger;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(TLogger.Log); //Incompatible types: Integer and TSeverity
end;
Introducing generics here does not help. The actual parameters that you have are not generic. They have fixed type, string and Integer. The function you are passing them to is not generic and receives parameters of type string and TSeverity. These types are mis-matched.
Generics won't help you here because your types are all known ahead of time. There is nothing generic here. What you need to do, somehow, is convert between Integer and TSeverity. Once you can do that then you can call your function.
In your case you should pass a procedure that accepts an Integer, since you don't have TSeverity available at the point where you call the procedure. Then in the implementation of that procedure, where you call the function that does accept a TSeverity, that's where you convert.
In scenarios involving generic procedural types, what you have encountered is quite common. You have a generic procedural type like this:
type
TMyGenericProcedure<T> = procedure(const Arg: T);
In order to call such a procedure you need an instance of T. If you are calling the procedure from a function that is generic on T, then your argument must also be generic. In your case that argument is not generic, it is fixed as Integer. At that point your attempt to use generics unravels.
Having said all of that, what you describe doesn't really hang together at all. How can you possibly come up with the severity argument if you don't know what TSeverity is at that point? That doesn't make any sense to me. How can you just conjure up an integer value and hope that it matches this enumerated type? Some mild re-design would enable you to do this quite simply without any type conversions.
As David Heffernan says, you cannot use generics in this way. Instead you should use a function to map the error level to a severity type, and use that to glue together the two. Based on your updated example, one could modify it like this:
unit uDoer;
interface
type
TLogProcedure = reference to procedure(const AMessage: String; AErrorLevel: Integer);
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
You can then provide the glue procedure which converts the error level to a severity:
implementation
uses
uDoer, uLogger;
function SeverityFromErrorLevel(const AErrorLevel: Integer): TSeverity;
begin
if (AErrorLevel <= 0) then
result := seInfo
else if (AErrorLevel = 1) then
result := seWarning
else
result := seError;
end;
procedure LogProc(const AMessage: String; AErrorLevel: Integer);
var
severity: TSeverity;
begin
severity := SeverityFromErrorLevel(AErrorLevel);
TLogger.Log(AMessage, severity);
end;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(LogProc);
end;
Note I didn't compile this, but the essence is there. I used a procedure reference (reference to procedure) as they're a lot more flexible, which may come in handy later.

Delphi Sent TObjectList like var parameter

I have class
TFolder = class
NODE_INDEX: Integer;
FIRST_INDEX : Integer;
CODE_NAME: AnsiString;
NAME: AnsiString;
constructor Create(NewNODE_INDEX, NewFIRST_INDEX: Integer; NewCODE_NAME, NewNAME: AnsiString);
destructor Destroy; override;
end;
And i have Type
type
TFolderList = class (TObjectList<TFolder>)
end;
Then i try to use this type
TAccount = class
...
FOLDERS: TFolderList;
public
constructor Create(...);
destructor Destroy; override;
procedure LoadFoldersFromDisk(var _objectList: TFolderList);
end;
When i try to send my TObject list like parameter
procedure TForm1.FormCreate(Sender: TObject);
begin
olLOCALFolders := TObjectList<TFolder>.Create();
Account.LoadFoldersFromDisk(olLOCALFolders);
end;
I get error "Types of actual and formal var parameters must be identical". What I'm doing wrong?
The error is because your sub-class is a new type, distinct from TObjectList<TFolder> and hence the error.
It is probably a mistake to derive a sub-class like this:
type
TFolderList = class (TObjectList<TFolder>)
end;
Doing so forces all parties to use that type and denies you the opportunity to take advantage of generic type compatibility. If you have a generic method operating on TObjectList<T> then your new type would be incompatible.
Instead declare an alias:
type
TFolderList = TObjectList<TFolder>;
The point about an alias is that it is a different name for the same type as opposed to a new type which your code declares.
Or simply use TObjectList<TFolder> everywhere without declaring TFolderList.
More broadly your TFolder type seems more suited to be a value type. I think it may be better as a record rather than a class.
Further, a var parameter appears incorrect. You would use a var parameter if the function was going to modify the reference. But it is going to populate the list that is passed in by the caller. You should remove the var from the argument list.
Just replace the TObjectList<TFolder> wtih the TFolderList you defined eariler:
procedure TForm1.FormCreate(Sender: TObject);
begin
olLOCALFolders := TFolderList.Create();
Account.LoadFoldersFromDisk(olLOCALFolders);
end;
However, you probably do not need to use var parameter here - the method name LoadFoldersFromDisk suggest that the method will populate the list sent as parameter with items, and for that you can send the list by value. You only need to use var parameter if the method would alert the list object's memory location (as opposed to it's content), ie when the LoadFoldersFromDisk could free the original list and create new one.

How to provide a derived instance with an ancestor field declaration?

I am having some trouble figuring out this case of inheritance.
In my class TBalans, I have a routine Initialiseer that takes a TBalPar object as parameter. TBalPar is the ancestor class of TNewBalPar that has additional fields. Now I would like to reach the additional fields from within my TBalans class. I still can feed a TNewBalPar object to the Initialiseer routine, but how do I get to the data of the descendant class?
What I tried is the following: I derived TBalans too into TNieuweBalans, gave it the new additional fields, and assign them in the routine:
type
TBalPar = class
//some vars
end;
TNewBalPar = class(TBalPar)
ExtraVar: TValue;
end;
TBalans = class
MyBalPar: TBalPar;
function Initialiseer(ABalPar: TBalPar): Boolean; virtual;
end;
TNieuweBalans = class(TBalans)
MyBalPar: TNewBalpar; //declared again so I don't need to cast it when using it
MyExtraVar: TValue;
function Initialiseer(ABalPar: TBalPar): Boolean; override;
end;
function TBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
MyBalPar := ABalPar;
end;
function TNieuweBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
inherited;
MyBalPar := TNewBalPar(ABalPar);
MyExtraVar := MyBalPar.ExtraVar; //instead of casting TNewBalPar(MyBalPar).ExtraVar
end;
This code works, but it feels wrong: I declare the MyBalPar field twice. I would like to improve on it.
Note that I am not looking for a way how to expose ExtraVar to the outside world, but how to use it conveniently within TNieuweBalans.
How can I eliminate the double MyBalPar field but still prevent frequent typecasting?
Current design
The need for a convenient designated derived field type for an ancestral field is not forbidden, nor uncommon for that matter. But your implementation, like you sense already, has some problems:
the doubled fields require unnecessary memory,
you need to synchronize changes to TBalans.MyBalPar and TNieuweBalans.MyBalPar,
you need to synchronize changes to TNieuweBalans.MyBalPar.ExtraVar and TNieuweBalans.MyExtraVar,
you do not enforce the derived class type: feeding a TBalPar object to TNieuweBalans.Initialiseer results in an access violation because MyBalPar.ExtraVar does not exist.
There are multiple ways to overcome each of these problems.
The most elementary solution to prevent extra fields is to provide properties for them with getters that extract the values from the inherited class (I renamed some of your types and variables for comprehensibility):
type
TBalPar = class(TObject)
// some variables
end;
TBalParEx = class(TBalPar)
private
FExtra: TValue;
public
property Extra: TValue read FExtra write FExtra;
end;
TBalance = class(TObject)
private
FBalPar: TBalPar;
public
function Initialize(ABalPar: TBalPar): Boolean; virtual;
property BalPar: TBalPar read FBalPar;
end;
TBalanceEx = class(TBalance)
private
function GetExtra: TValue;
procedure SetExtra(Value: TValue);
public
function BalPar: TBalParEx;
function Initialize(ABalPar: TBalPar): Boolean; override;
property Extra: TValue read GetExtra write SetExtra;
end;
function TBalanceEx.BalPar: TBalParEx;
begin
Result := TBalParEx(inherited BalPar);
end;
function TBalanceEx.GetExtra: TValue;
begin
Result := BalPar.Extra;
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
BalPar.Extra := Value;
end;
With this approach, there is only one typecast needed and it does not require additional storage.
To enforce TBalanceEx.BalPar to be of type TBalParEx, you could raise an exception in the Initialize routine:
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := True;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
if ABalPar is TBalParEx then
Result := inherited Initialize(ABalPar)
else
raise Exception.Create('Wrong BalPar type');
end;
Of course, this places the sole responsibility of a correct class functioning on the requirement to always call the Initialize routine before any other usage of the other class members. Since that is what initialization obviously is intended for, you could ignore that, but protection against misuse could be added like:
TBalance = class(TObject)
protected
function HasBalPar: Boolean; virtual;
...
TBalanceEx = class(TBalance)
protected
function HasBalPar: Boolean; override;
...
function TBalance.HasBalPar: Boolean;
begin
Result := FBalPar is TBalPar;
end;
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := HasPalBar;
end;
function TBalanceEx.GetExtra: TValue;
begin
if HasBalPar then
Result := BalPar.Extra
else
Result := nil;
end;
function TBalanceEx.HasBalPar: Boolean;
begin
Result := BalPar is TBalParEx;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
Result := inherited Initialize(ABalPar);
if Result = False then
raise Exception.Create('Initialization went wrong');
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
if HasBalPar then
BalPar.Extra := Value;
end;
In turn, this requires not to forget to implement HasBalPar for each derived class. You could 'protect' against that with:
TBalance = class(TObject)
strict private
function HasBalPar: Boolean;
private
...
TBalanceEx = class(TBalance)
strict private
function HasBalPar: Boolean;
private
...
Design considerations
All in all, making this a robust design requires some work. And your current approach raises the question why you would want to have the Extra field in the TBalanceEx class too. Even why to have a TBalanceEx class at all.
From the naming of your classes, I assume you have the following equivalent: A structure which has structural parameters like build date, owner, location, and you have a specialized structure, say a castle, with additional parameters like the number of towers and whether it has a moat:
TStructureData: Location, BuildDate
TCastleData: Location, BuildDate, TowerCount, HasMoat
TStructure: StructureData
TCastle: StructureData, CastleData
The question you need to answer is whether a structure needs to know if it is a castle, or a palace, a warehouse, a biological or chemical structure. Assume your program evolves to being able to handle all different kinds of structures, then you are always bound to add two classes to your program, resulting in a more and more complex and improvised design which in the end will get you in trouble, if not already. The challenge is to make this a more generalized and abstract design.
For example:
must TStructureData and TStructure be separate classes?
could calculations, analysations, or presentational requests on the specific data be 'outsourced' to the specific class? E.g.: if you add a GetFeatures routine to the TStructureData class, then the TStructure class can request the features of a TCastle without knowing it being a Castle.
...
Think big.

Delphi - how to enum string to type

I got strings in database like 'TGroupBox' or 'TEdit' ... now I need to check element against them... how do I enumerate string to type?
I mean something like this:
mystr := 'TGroupBox';
If (page.Controls[0] is mystr) then ...
Of course it won't work, as error appears:
E2015 Operator not applicable to this operand type
How do I do that correctly?
You can verify that
page.Controls[0].ClassName = mystr
using the ClassName property.
But notice that this doesn't do exactly the same thing as the is operator. To see the difference, suppose you have a class TFruit and a subclass TApple. If myFruit is an instance of a TApple, then both myFruit is TApple and myFruit is TFruit will yield true. But of course, the ClassName will still only be TApple.
If you need the full functionality of the is operator, you can make use of the ClassParent property, as suggested by hvd:
function IsDerivedFrom(AClass: TClass; const AClassName: string): boolean;
begin
if not Assigned(AClass) then Exit(false);
result := SameText(AClass.ClassName, AClassName) or
IsDerivedFrom(AClass.ClassParent, AClassName);
end;
To get the class of an object, use the ClassType property:
IsDerivedFrom(page.Controls[0].ClassType, mystr);
The function you are looking for is GetClass located in System.Classes. Be aware that the class has to be registered.
System.Classes.GetClass
For the specific scenario in the question body the answer by Andreas Rejbrand (with assistance from hvd) is a good one. However, for the broader problem implied by the question title - how to I convert a string containing a class name to a class reference? - you can utilise extended RTTI in a new(ish) version of Delphi:
unit ClassLookupUtils;
interface
uses
System.SysUtils, System.Generics.Collections, System.Rtti;
type
RttiClassLookup = record
strict private
class var FMap: TDictionary<string, TClass>;
class destructor Destroy;
public
class function Find(const ClassName: string): TClass; static;
end;
implementation
class destructor RttiClassLookup.Destroy;
begin
FMap.Free;
end;
class function RttiClassLookup.Find(const ClassName: string): TClass;
var
RttiType: TRttiType;
RttiContext: TRttiContext;
begin
if FMap = nil then
begin
FMap := TDictionary<string, TClass>.Create;
for RttiType in RttiContext.GetTypes do
if RttiType is TRttiInstanceType then
FMap.AddOrSetValue(RttiType.Name.ToLowerInvariant, (RttiType as TRttiInstanceType).MetaclassType);
end;
if not FMap.TryGetValue(ClassName.ToLowerInvariant, Result) then
Result := nil;
end;
end.
In use:
var
MyStr: string;
MyStrClass: TClass;
begin
//...
MyStrClass := RttiClassLookup.Find(MyStr);
if MyStrClass <> nil then
for I := 0 to Page.ControlCount - 1 do
if Page.Controls[I].InheritsFrom(MyStrClass) then
begin
//...
end;
The background here is that SomeObj is SomeClass is implemented as (SomeObj <> nil) and SomeObj.InheritsFrom(SomeClass).
You have a good answer from #UweRaabe usingRTTIto getClassName.
A simple (and not very robust) hack without using RTTI would be to use the TComponent.Name property, which is a string, like this - without the is operator:
If (pos('GroupBox', page.Controls[0].name)>0 ) then ...
By default, a control gets the same name as the instance variable, so GroupBox1.name='GroupBox1'. You can either change your database entries to use the substr 'groupbox' or extract 'groupbox' from the type name string in your database.
That being said, if you've inherited this design approach of persisting type names as strings in a database and then using them at runtime to check the types of different components, then you're stuck with it, and so be it. But Delphi is a strongly typed, compiled language, so persisting type names as strings in a database and reading them at runtime and decoding them into Delphi types just doesn't "smell right" IMO. I would re-think this design if possible. Consider doing it all in Delphi using classOf type, enumerations, etc.

What is the difference between of object and reference to?

What is the difference between
TFuncOfIntToString = reference to function(x: Integer): string;
and
TFuncOfIntToString = function(x: Integer): string of object;
I use the of object
Let us consider the following three type declarations:
TProcedure = procedure;
TMethod = procedure of object;
TAnonMethod = reference to procedure;
These are all very similar to each other. In terms of calling instances of each of these three types, the calling code is identical. The differences arise in what can be assigned to variables of these types.
Procedural types
TProcedure is a procedural type. You can assign to a variable of type TProcedure something of this form:
procedure MyProcedure;
begin
end;
This is a non object-oriented procedure. You cannot assign an instance or class method to a TProcedure variable. However, you can assign a static class method to a TProcedure variable.
Method pointers
TMethod is a method pointer. This is indicated by the presence of of object. When you have a variable of type TMethod you must assign either:
A instance method of an instantiated object, or
A class method.
So you can assign either of these:
procedure TMyClass.MyMethod;
begin
end;
class procedure TMyClass.MyClassMethod;
begin
end;
The big difference between a procedural type and a method pointer is that the latter contains a reference to both code and data. A method pointer is often known as a two-pointer procedural type. A variable that contains a method pointer contains references to the code and the instance/class to call it on.
Consider the following code:
var
instance1, instance2: TMyClass;
method1, method2: TMethod;
....
method1 := instance1.MyMethod;
method2 := instance2.MyMethod;
Now, although method1 and method2 refer to the same piece of code, they are associated with different object instances. So, if we call
method1();
method2();
We are invoking MyMethod on the two distinct instances. That code is equivalent to:
instance1.MyMethod();
instance2.MyMethod();
Anonymous methods
Finally we come to anonymous methods. These are even more general purpose than procedural types and method pointers. You can assign any of the following to a variable defined using the reference to syntax:
A plain non object-oriented procedure.
An instance method of an instantiated class.
A class method.
An anonymous method.
For example:
var
AnonMethod: TAnonMethod;
....
AnonMethod := MyProcedure; // item 1 above
AnonMethod := instance1.MyMethod; // item 2
AnonMethod := TMyClass.MyClassMethod; // item 3
Anonymous methods, item 4 above, are those declared in-line in your code. For example:
var
AnonMethod: TAnonMethod;
....
AnonMethod := procedure
begin
DoSomething;
end;
The biggest benefit of anonymous methods when compared to the procedural types and method pointers is that they allow for variable capture. For example consider the following short program to illustrate:
{$APPTYPE CONSOLE}
program VariableCapture;
type
TMyFunc = reference to function(X: Integer): Integer;
function MakeFunc(Y: Integer): TMyFunc;
begin
Result := function(X: Integer): Integer
begin
Result := X*Y;
end;
end;
var
func1, func2: TMyFunc;
begin
func1 := MakeFunc(3);
func2 := MakeFunc(-42);
Writeln(func1(4));
Writeln(func2(2));
Readln;
end.
This has the following output:
12
-84
The first is anonymous method, the second is ordinary method.

Resources