为什么在此匿名方法中重置捕获的参数?

时间:2014-04-25 21:01:38

标签: delphi delphi-xe5 anonymous-methods

以下代码基于以下文章:http://blog.barrkel.com/2010/01/using-anonymous-methods-in-method.html

当触发匿名过程中的事件处理程序代码时(在更改网格中的行时),第一个' if'报告说dbgrid不是nil,但第二个报告它是nil。

有关这里发生了什么的任何想法?您可以从here获取完整的源代码(您可能必须更改TClientDataSet的FileName属性以指向解压缩项目的目录。)

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Data.DB, Vcl.DBGrids, Datasnap.DBClient, Vcl.Grids;

type

  AfterScrollEventHandler = reference to procedure (sender: TDataSet);

  TForm3 = class(TForm)
    dbgrdGrid: TDBGrid;
    cdsDataSet: TClientDataSet;
    dsDataSet: TDataSource;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  procedure MethodReferenceToMethodPtr(const MethRef; var MethPtr);
  procedure InjectEventHandler(dataSet: TDataSet; dbGrid: TDBGrid);

var
  Form3: TForm3;

implementation

{$R *.dfm}
procedure InjectEventHandler(dataSet: TDataSet; dbGrid: TDBGrid);
var
  eventHandlerRef : AfterScrollEventHandler;
  eventHandlerPtr : TDataSetNotifyEvent;
begin

  eventHandlerRef := procedure (sender: TDataSet)
    begin
      if dbGrid <> nil then
        MessageDlg('1: AfterScroll: The grid is not nil!', mtInformation, [mbOK], 0)
      else
        MessageDlg('1: AfterScroll: The grid is nil! It''s going to crash...', mtInformation, [mbOK], 0);

      if dbGrid <> nil then
        MessageDlg('2: AfterScroll: The grid is not nil!', mtInformation, [mbOK], 0)
      else
        MessageDlg('2: AfterScroll: The grid is nil! It''s going to crash...', mtInformation, [mbOK], 0);

    end;

  MethodReferenceToMethodPtr (eventHandlerRef, eventHandlerPtr);

  dataSet.AfterScroll := eventHandlerPtr;

end;



procedure MethodReferenceToMethodPtr(const MethRef; var MethPtr);
type
  TVtable = array[0..3] of Pointer;
  PVtable = ^TVtable;
  PPVtable = ^PVtable;
begin
  // 3 is offset of Invoke, after QI, AddRef, Release
  TMethod(MethPtr).Code := PPVtable(MethRef)^^[3];
  TMethod(MethPtr).Data := Pointer(MethRef);
end;

procedure TForm3.FormCreate(Sender: TObject);
var
  eventHandlerRef1, eventHandlerRef2 : AfterScrollEventHandler;
begin

  InjectEventHandler(cdsDataSet, dbgrdGrid)
end;

end.

更新

此代码按预期工作(匿名过程将dbGrid参数存储在本地变量中):

procedure InjectEventHandler(dataSet: TDataSet; dbGrid: TDBGrid);
var
  eventHandlerRef : AfterScrollEventHandler;
  eventHandlerPtr : TDataSetNotifyEvent;
begin

  eventHandlerRef := procedure (sender: TDataSet)
    var grid: TDBGrid;
    begin
      grid := dbGrid;
      if grid <> nil then
        MessageDlg('1: AfterScroll: The grid is not nil!', mtInformation, [mbOK], 0)
      else
        MessageDlg('1: AfterScroll: The grid is nil! It''s going to crash...', mtInformation, [mbOK], 0);

      if grid <> nil then
        MessageDlg('2: AfterScroll: The grid is not nil!', mtInformation, [mbOK], 0)
      else
        MessageDlg('2: AfterScroll: The grid is nil! It''s going to crash...', mtInformation, [mbOK], 0);

    end;

  MethodReferenceToMethodPtr (eventHandlerRef, eventHandlerPtr);

  dataSet.AfterScroll := eventHandlerPtr;

end;

1 个答案:

答案 0 :(得分:3)

您看到未定义的行为。您引用的文章提到方法引用需要在方法指针的生命周期内保持活动状态,但您的代码会破坏该规则。与方法引用关联的对象可能会被销毁,因此保留捕获值的dbGrid变量不再存在。您正在读取垃圾值,并且内存中的位置可能会在第一次和第二次读取之间更改值。我们甚至不知道你读的值是否等于你期望的值,只是它不是零。

当您使用局部变量时似乎有效,因为在第一次和第二次读取之间写入的内存显然不再与函数期望dbGridgrid变量驻留的位置重叠。不过,你还在读垃圾。它只是垃圾而不是两次而不是一次。

使您的eventHandlerRef变量成为封闭类的字段而不是局部变量,并且一切都应该很好,假设Delphi 2010的未记录的实现细节在您现在使用的版本中仍然有效。< / p>