Frage

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?

War es hilfreich?

Lösung

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;

Andere Tipps

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:

  1. 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.

  2. 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);
    
  3. 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top