以下代码基于以下文章: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;
答案 0 :(得分:3)
您看到未定义的行为。您引用的文章提到方法引用需要在方法指针的生命周期内保持活动状态,但您的代码会破坏该规则。与方法引用关联的对象可能会被销毁,因此保留捕获值的dbGrid
变量不再存在。您正在读取垃圾值,并且内存中的位置可能会在第一次和第二次读取之间更改值。我们甚至不知道你读的值是否等于你期望的值,只是它不是零。
当您使用局部变量时似乎有效,因为在第一次和第二次读取之间写入的内存显然不再与函数期望dbGrid
或grid
变量驻留的位置重叠。不过,你还在读垃圾。它只是垃圾而不是两次而不是一次。
使您的eventHandlerRef
变量成为封闭类的字段而不是局部变量,并且一切都应该很好,假设Delphi 2010的未记录的实现细节在您现在使用的版本中仍然有效。< / p>