Как и когда используются ссылки на переменные в Delphi ' s захвачены анонимные методы?

Переменные среды процесса существуют во времени выполнения и не хранятся в некотором файле или около этого. Они хранятся в собственной памяти процесса (это - то, где они, как находят, переходят к детям). Но существует виртуальный файл в

/proc/pid/environ

, Этот файл показывает все переменные среды, которые были переданы при вызове процесса (если процесс не перезаписал ту часть своей памяти — большинство программ, не делают). Ядро делает их видимыми через тот виртуальный файл. Можно перечислить их. Например, для просмотра переменных процесса 3940 можно сделать

cat /proc/3940/environ | tr '\0' '\n'

, Каждая переменная разграничена двоичным нулем от следующего. TR заменяет нуль в новую строку.

22
задан Community 23 May 2017 в 11:47
поделиться

1 ответ

Когда у вас есть такая функция, как в вопросе, где у вас есть анонимный метод для доступа к локальной переменной, Delphi создает одного потомка TInterfacedObject, который захватывает все переменные на основе стека как свои собственные открытые переменные. Используя трюк Барри, чтобы добраться до реализации TObject и немного RTTI, мы можем увидеть все это в действии.

Волшебный код реализации, вероятно, выглядит следующим образом:

// Magic object that holds what would normally be Stack variables and implements
// anonymous methods.
type ProcedureThatUsesAnonymousMethods$ActRec = class(TInterfacedObject)
public
  V: string;
  function AnonMethodImp: string;
end;

// The procedure with all the magic brought to light
procedure ProcedureThatUsesAnonymousMethods;
var MagicInterface: IUnknown;
    F1: TFunc<string>;
    F2: TFunc<string>;
begin
  MagicInterface := ProcedureThatUsesAnonymousMethods$ActRec.Create;
  try
    F1 := MagicInterface.AnonMethod;
    MagicInterface.V := '1';
    F2 := MagicInterface.SomeOtherAnonMethod;
    MagicInterface.V := '2';
    ShowMessage(F1);
    ShowMessage(F2);
  finally MagicInterface := nil;
  end;
end;

Конечно, этот код не компилируется. Я без магии :-) Но идея здесь в том, что за кулисами создается «магический» объект, а локальные переменные, на которые ссылается анонимный метод, преобразуются в открытые поля магического объекта. Этот объект используется в качестве интерфейса (IUnkown), поэтому он получает счетчик ссылок. По-видимому, один и тот же объект захватывает все используемые переменные И определяет все анонимные методы.

Это должно отвечать и «когда», и «как».

Вот код, который я использовал для расследования. Поместите TButton в чистую форму, это должна быть целая единица. Когда вы нажмете кнопку, вы увидите следующее на экране в последовательности:

  • 000000 (поддельное число)
  • 000000 (одно и то же число): Это доказывает, что оба анонимных метода фактически реализованы как методы одного и того же объекта!
  • TForm25.Button1Click$ActRec: TInterfacedObject: Здесь показан объект, стоящий за реализацией, он получен из TInterfacedObject
  • OnStack:string: RTTI обнаруживает это поле в этом объекте.
  • Self: TForm25: RTTI обнаруживает это поле на этом объекте. Он используется для получения значения ClasVar
  • FRefCount:Integer - это происходит из TInterfacedObject
  • Class Var - результат ShowMessage.
  • On Stack - результат ShowMessage.

Вот код:

unit Unit25;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Rtti;

type
  TForm25 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    ClassVar: string;
  public
  end;

var
  Form25: TForm25;

implementation

{$R *.dfm}

procedure TForm25.Button1Click(Sender: TObject);
var F1: TFunc<string>;
    F2: TFunc<string>;

    OnStack: string;

    i: IInterface;
    o: TObject;

    RC: TRttiContext;
    R: TRttiType;
    RF: TRttiField;

begin
  // This anonymous method references a member field of the TForm class
  F1 := function :string
        begin
          Result := ClassVar;
        end;

  i := PUnknown(@F1)^;
  o := i as TObject;
  ShowMessage(IntToStr(Integer(o))); // I'm looking at the pointer to see if it's the same instance as the one for the other Anonymous method

  // This anonymous method references a stack variable
  F2 := function :string
        begin
          Result := OnStack;
        end;

  i := PUnknown(@F2)^;
  o := i as TObject;
  ShowMessage(IntToStr(Integer(o)));

  ShowMessage(o.ClassName + ': ' + o.ClassType.ClassParent.ClassName);

  RC.Create;
  try
    R := RC.GetType(o.ClassType);
    for RF in R.GetFields do
      ShowMessage(RF.Name + ':' + RF.FieldType.Name);
  finally RC.Free;
  end;

  ClassVar := 'Class Var';
  OnStack := 'On Stack';

  ShowMessage(F1);
  ShowMessage(F2);
end;

end.
27
ответ дан 29 November 2019 в 05:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: