在 Delphi 中实现函数指针堆栈

Implement stack of function pointers in Delphi

我们已经声明了一个可以用作进度回调的类型(例如从一个巨大的日志文件中每 10,000 行加载一次):

// Declared in some base unit
TProcedureCallback = procedure() of object;

// Declared in the class that loads the events
procedure ReadEvents(callback: TProcedureCallback);

// Implementation of above method
procedure TEvents.ReadEvents(callback: TProcedureCallback);
var
    nEvents: Integer;
begin
    nEvents := 0;

    // Read some events...
    Inc(nEvents);
    // ...and repeat until end of log file

    // Every 10,000 events, let the caller know (so they update
    // something like a progress bar)
    if ((nEvents mod 10000) = 0) then
        callback();
end;

// And the caller uses it like this
public
    procedure EventsLoadCallBack();

// Implementation of callback
procedure TfrmLoadEvents.EventsLoadCallBack();
begin
    // Update some GUI control...
end;

// And the events are loaded like this
events.ReadEvents(EventsLoadCallBack);

这一切都很好......但我想将其扩展到 TObjectStack 容器,以便我们可以实现自动注销功能。这个想法是,在创建每个表单时,它都会注册一个回调(即,将其推送到某个系统范围的堆栈上)。当表单被销毁时,它会将回调从堆栈中弹出。如果发生自动注销,您只需展开堆栈和 return 用户到主窗体,然后执行与自动注销相关的其余工作。

但是,我无法让它工作...当我尝试将 TProcedureCallback 对象压入堆栈时,出现编译器错误:

// Using generic containers unit from Delphi 7
uses
  Contnrs;

// Declare stack
stackAutoLogOff: TObjectStack;

// Initialise stack
stackAutoLogOff := TObjectStack.Create();

// Attempt to use stack
stackAutoLogOff.Push(callback);
stackAutoLogOff.Push(TObject(callback));

// Clean up...
stackstackAutoLogOff.Free();

第一个 returns Incompatible types 和第二个 Invalid typecast。函数指针栈的正确实现方式是什么?

问题是 TObjectStack 期望包含 TObject 类型的对象,而您的回调是 TMethod 类型,它是包含两个指针的记录。

如果您使用的是 Delphi 的现代版本,一个简单的解决方案是使用泛型。例如:

 TObjectProc = procedure of object;
 TMyCallbackStack = TStack<TObjectProc>;

如果没有泛型,您将需要构建自己的堆栈 class 来管理回调的存储。这是一个相当简单的 class 构建,在最基本的情况下,可能看起来像这样:

program Project1;
{$APPTYPE CONSOLE}

uses
  SysUtils;
type
  TMyClass = class
    procedure foo;
  end;

  TObjProc = procedure of object;
  TObjProcStack = class(TObject)
    private
      FList: array of TObjProc;
    public
      function Count: Integer;
      procedure Push(AItem: TObjProc);
      function Pop: TObjProc; inline;
      function Peek: TObjProc; inline;
  end;


function TObjProcStack.Peek: TObjProc;
begin
  Result := FList[Length(FList)-1];
end;

function TObjProcStack.Pop: TObjProc;
begin
  Result := Peek();
  SetLength(FList, Length(FList) - 1);
end;

procedure TObjProcStack.Push(AItem: TObjProc);
begin
  SetLength(FList, Length(FList) + 1);
  FList[Length(FList)-1] := AItem;
end;

function TObjProcStack.Count: Integer;
begin
  Result := Length(FList);
end;


{TMyClass}
procedure TMyClass.Foo;
begin
  WriteLn('foo');
end;

var
  LMyClass : TMyClass;
  LStack : TObjProcStack;
begin
  LStack := TObjProcStack.Create;
  LMyClass := TMyClass.Create;
  try
    LStack.Push(LMyClass.foo);
    LStack.Pop;   {executes TMyClass.Foo - outputs 'foo' to console}
  finally
    LStack.Free;
    LMyClass.Free;
  end;
  ReadLn;
end.

您可以将回调包装在对象中,然后使用标准堆栈选项。通过将 that 包装在您自己的 class 中,您就有了一个完整的解决方案,如下所示:

unit UnitCallbackStack;

interface

uses
  Contnrs;

type
  TProcedureCallback = procedure() of object;


type
  TMyCallbackObject = class    // wrapper for callback
  private
    FCallBack : TProcedureCallback;
  protected
  public
    constructor Create( ACallback : TProcedureCallback ); reintroduce;
    property CallBack : TProcedureCallback
             read FCallBack;
  end;

type
  TCallBackStack = class( TObjectStack)
  private
  public
    function Push(ACallback: TProcedureCallback): TProcedureCallback; reintroduce;
    function Pop: TProcedureCallback; reintroduce;
    function Peek: TProcedureCallback; reintroduce;

  end;

implementation

{ TCallBackStack }

function TCallBackStack.Peek: TProcedureCallback;
var
  iObject : TMyCallbackObject;
begin
  iObject := inherited Peek as TMyCallbackObject;
  if assigned( iObject ) then
  begin
    Result := iObject.CallBack; // no delete here as reference not removed
  end
  else
  begin
    Result := nil;
  end;
end;

function TCallBackStack.Pop: TProcedureCallback;
var
  iObject : TMyCallbackObject;
begin
  iObject := inherited Pop as TMyCallbackObject;
  if assigned( iObject ) then
  begin
    Result := iObject.CallBack;
    iObject.Free; // popped, so no longer needed
  end
  else
  begin
    Result := nil;
  end;
end;

function TCallBackStack.Push(ACallback: TProcedureCallback): TProcedureCallback;
begin
  inherited Push( TMyCallbackObject.Create( ACallBack ));
end;


{ TMyCallbackObject }

constructor TMyCallbackObject.Create(ACallback: TProcedureCallback);
begin
  inherited Create;
  fCallBack := ACallBack;
end;

end.

然后您可以按照尝试使用 TStack 的方式使用 TCallBackStack。