delphi - 为什么在这个匿名方法中捕获的参数会被重置?

标签 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;

最佳答案

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

当您使用局部变量时,它似乎可以工作,因为在第一次和第二次读取之间写入的任何内存显然不再与函数期望 dbGrid 或 grid 的位置重叠> 驻留变量。但你仍然在读垃圾。这只是垃圾,碰巧不是 nil 两次而不是一次。

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

关于delphi - 为什么在这个匿名方法中捕获的参数会被重置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23302972/

相关文章:

c# - 在 C# 中从字符串创建匿名方法

java - 如何实现比较器来比较名称?

c# - 匿名方法源码

delphi - 在组件安装期间调试包

android - 如何使用 Intent 发送多个附件

delphi - 应用程序图标卡住 DELPHI XE5

delphi - 什么可以使带有 VBO 的 glDrawArrays 无法绘制任何内容?

asp.net - 无法删除 TWebBrowser 的边框和滚动条?

windows - 通过 GetModuleHandle/LoadLibrary 和使用 FreeLibrary 加载 DLL

delphi - 如何自动调整 TStringGrid 行的大小