Reputation: 76670
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?
Upvotes: 3
Views: 184
Reputation: 163317
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.
Upvotes: 3
Reputation: 34919
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
.
Upvotes: 2
Reputation: 613262
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;
Upvotes: 3