Как получить доступ к частным методам без помощников?

в Delphi 10 Seattle я мог бы использовать следующий код для работы с чрезмерно строгими ограничениями видимости.

как получить доступ к приватным переменным?

type 
  TBase = class(TObject)
  private
    FMemberVar: integer;
  end;

и как получить доступ к простым или виртуальным частным методам?

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

ранее я бы использовал помощник класса для взлома базового класса.

type
  TBaseHelper = class helper for TBase
    function GetMemberVar: integer;

в Delphi 10.1 Berlin помощники классов больше не имеют доступа к частным членам предметный класс или запись.

есть ли альтернативный способ доступа к частным членам?

5 ответов


если есть расширенная информация RTTI, генерируемая для частных членов класса - полей и/или методов, которые вы можете использовать для получения доступа к ним.

конечно, доступ через RTTI намного медленнее, чем через помощников класса.

доступа к методам:

var
  Base: TBase2;
  Method: TRttiMethod;

  Method := TRttiContext.Create.GetType(TBase2).GetMethod('UsefullButHidden');
  Method.Invoke(Base, []);

доступ к переменным:

var
  Base: TBase;
  v: TValue;

  v := TRttiContext.Create.GetType(TBase).GetField('FMemberVar').GetValue(Base);

информация RTTI по умолчанию генерируется для классов RTL / VCL/FMX после

  • поля - private, protected, public, published
  • методы public, published
  • "свойства" - public, published

к сожалению, это означает, что доступ к частным методам через RTTI для основных библиотек Delphi недоступен. ответ@LU RD охватывает взлом, который позволяет Частный доступ к методу для классов без расширенного RTTI.

работа с Библиотеку RTTI


есть еще способ использовать class helpers для доступа частные методы в Delphi 10.1 Berlin:

type
  TBase2 = class(TObject) 
  private
    procedure UsefullButHidden;  
    procedure VirtualHidden; virtual;
    procedure PreviouslyProtected; override;
  end;

  TBase2Helper = class helper for TBase2
    procedure OpenAccess;
  end;

  procedure TBase2Helper.OpenAccess;
  var
    P : procedure of object;
  begin
    TMethod(P).Code := @TBase2.UsefullButHidden;
    TMethod(P).Data := Self;
    P; // Call UsefullButHidden;
    // etc
  end;

к сожалению, нет способа получить доступ строгие частные / частные поля помощниками класса с Delphi 10.1 Berlin. RTTI является вариантом, но может считаться медленным, если производительность критична.

вот способ определить смещение в поле при запуске с помощью помощников классов и RTTI:

type 
  TBase = class(TObject)
  private  // Or strict private
    FMemberVar: integer;
  end;

type
  TBaseHelper = class helper for TBase
  private
    class var MemberVarOffset: Integer;
    function GetMemberVar: Integer;
    procedure SetMemberVar(value: Integer);
  public
    class constructor Create;  // Executed at program start
    property MemberVar : Integer read GetMemberVar write SetMemberVar;
  end;

class constructor TBaseHelper.Create;
var
  ctx: TRTTIContext;
begin
  MemberVarOffset := ctx.GetType(TBase).GetField('FMemberVar').Offset;
end;

function TBaseHelper.GetMemberVar: Integer;
begin
  Result := PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^;
end;

procedure TBaseHelper.SetMemberVar(value: Integer);
begin
  PInteger(Pointer(NativeInt(Self) + MemberVarOffset))^ := value;
end;

это имейте преимущество, что медленная часть RTTI выполняется только один раз.


Примечание: использование RTTI для доступа защищенных / частных методов

RTL/VCL/FMX не объявили видимость для доступа защищенных / частных методов с RTTI. Он должен быть установлен с локальной директивой {$RTTI}.

используя RTTI для доступа частных / защищенных методов в другом коде требуется, например, установка:

{$RTTI EXPLICIT METHODS([vcPublic, vcProtected, vcPrivate])}

Если вы хотите чистый способ, который не влияет на производительность, вы все еще можете получить доступ к частным полям из помощника записи с помощью оператора with.

function TValueHelper.GetAsInteger: Integer;
begin
  with Self do begin
    Result := FData.FAsSLong;
  end;
end;

Я надеюсь, что они сохранят этот метод открытым, потому что у нас есть код с высокими требованиями к производительности.


предполагая, что расширенный RTTI недоступен, то, не прибегая к тому, что будет считаться взломом, вы не можете получить доступ к частным членам из кода в другом блоке. Конечно, если RTTI доступен, его можно использовать.

насколько я понимаю, возможность взлома частных членов с помощью помощников была непреднамеренной случайностью. Цель состоит в том, чтобы частные члены были видны только из кода в том же блоке, а строгие частные члены были видны только из кода в тот же класс. Это изменение исправляет аварию.

без возможности компилятора взломать класс для вас, вам нужно будет прибегнуть к другим способам сделать это. Например, вы можете повторно объявить достаточно TBase class, чтобы иметь возможность обмануть компилятор, чтобы сказать вам, где жил член.

type
  THackBase = class(TObject)
  private
    FMemberVar: integer;
  end;

теперь вы можете написать

var
  obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;  

но это ужасно хрупко и сломается, как только макет TBase is измененный.

это будет работать для членов данных, но для не виртуальных методов вам, вероятно, придется использовать методы разборки среды выполнения, чтобы найти местоположение кода. Для виртуальных членов этот метод можно использовать для поиска смещения VMT.

Читать далее:


Если вам не нужна поддержка компилятора ARM, вы можете найти другое решение здесь.

С встроенным asembler, вы можете получить доступ к частному полю или методу, легко.

Я думаю Дэвид лучше, в большинстве случаев, но если вам нужен быстрый решение на огромный класса, этот метод может быть более полезным.

Обновление(17 Июня): Я только что заметил, я забыл поделиться своим пример кода для доступ к закрытым полям из его в должности. извиняюсь.

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.

unit UnitB;

type
  THogeHelper = class helper for THoge
  public
    function GetValue: Integer;
    procedure CallMethod;
  end;

function THogeHelper.GetValue: Integer;
asm
  MOV EAX,Self.FPrivateValue
end;

procedure THogeHelper.CallMethod;
asm
  CALL THoge.PrivateMethod
end;

вот его пример кода для вызов частного метода.

type
  THoge = class
  private
    procedure PrivateMethod (Arg1, Arg2, Arg3 : Integer);
  end;

// Method 1
// Get only method pointer (if such there is a need to assign a method pointer to somewhere)
type
  THogePrivateProc = procedure (Self: THoge; Arg1, Arg2, Arg3: Integer);
  THogePrivateMethod = procedure (Arg1, Arg2, Arg3: Integer) of object;

function THogeHelper.GetMethodAddr: Pointer;
asm
  {$ifdef CPUX86}
  LEA EAX, THoge.PrivateMethod
  {$else}
  LEA RAX, THoge.PrivateMethod
  {$endif}
end;

var
  hoge: THoge;
  proc: THogePrivateProc;
  method: THogePrivateMethod;
begin
  // You can either in here of the way,
  proc := hoge.GetMethodAddr;
  proc (hoge, 1, 2, 3);
  // Even here of how good
  TMethod (method) .Code := hoge.GetMethodAddr;
  TMethod (method) .Data := hoge;
  method (1, 2, 3) ;
end;

// Method 2
// To jump (here is simple if you just simply call)
procedure THogeHelper.CallMethod (Arg1, Arg2, Arg3 : Integer);
asm
  JMP THoge.PrivateMethod
end;

unit UnitA;

type
  THoge = class
  private
    FPrivateValue: Integer;
    procedure PrivateMethod;
  end;
end.