В 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 помощники класса больше не имеют доступа к закрытым членам предметного класса или записи.

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

31
Johan 19 Апр 2016 в 13:57

6 ответов

Лучший ответ

Если существует расширенная информация 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

23
Community 23 Май 2017 в 12:09

Еще есть способ использовать 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])}
22
LU RD 19 Июн 2016 в 11:07

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

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

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

14
Toon Krijthe 21 Мар 2017 в 19:54

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

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

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

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

Теперь ты можешь писать

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

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

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

Дальнейшее чтение:

11
David Heffernan 19 Апр 2016 в 12:25

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

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

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

Обновление (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.
4
Community 23 Май 2017 в 10:31

Просто используйте оператор 'with' для доступа к закрытым полям!

См. Пример кода ниже, взятого из этой статьи, которую я заметил сегодня. (Спасибо, господин DEKO, как всегда!)

Изначально об этом взломе сообщалось на QualityPortal в августе 2019 г., как описано в пункте выше. (требуется вход)


перед перезаписью (с использованием метода "asm")

function TPropertyEditorHelper.GetPropList: PInstPropList;
{$IF CompilerVersion < 31.0}
begin
  Result := Self.FPropList;
end;
{$ELSE}
// http://d.hatena.ne.jp/tales/20160420/1461081751
asm
  MOV EAX, Self.FPropList;
end;
{$IFEND}

переписать, используя "с"

function TPropertyEditorHelper.GetPropList: PInstPropList;
begin
  with Self do
    Result := FPropList;
end;

Я был поражен, что это так просто :-)

2
benok 28 Ноя 2019 в 10:12