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

28

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

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

  • 7
    Вот это да. Это действительно отстой. Я думаю, что в прошлом я когда-либо использовал взломщики классов только для исправления или расширения поврежденных или дефектных объектов RTL / VCL. Это никогда не бывает элегантно, но как временный обходной путь до тех пор, пока Emba не исправит свою кодовую базу, они стали весьма ценными. Кажется безумным, что они могут нанести ущерб функции, которая помогает нам управлять их различными ненадежными библиотеками платформы ...
  • 1
    @Johan Это единственный вопрос с тегом delphi-10-berlin - просто удалите его и дайте ему умереть.
Показать ещё 1 комментарий
Теги:
private
delphi-10.1-berlin

5 ответов

21
Лучший ответ

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

  • 0
    Попробуйте M := TRttiContext.Create.GetType(TCustomForm).GetMethod('SetWindowState'); где M является TRttiMethod . Вы найдете это nil . Очевидно, что не все классы VCL / RTL / FMX имеют расширенные сгенерированные RTTI.
  • 0
    Невозможно получить доступ к защищенным / закрытым методам в RTL / FMX / VCL с RTTI. Он управляется директивой {$ RTTI} , которая является локальной по объему.
Показать ещё 4 комментария
18

Существует еще способ использовать 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])}
  • 0
    Хорошая находка о классе помощников. Но это показывает, что они еще не полностью удалили ошибку. Конечно, это может быть преднамеренным.
  • 7
    @RudyVelthuis, Сложно понять, почему Emba считает, что помощнику по классу следует запретить иметь полный доступ к классу. Помощник класса по определению сам класс. Конечно, они могут делать то, что хотят, но выделять эту функцию, когда они могут использовать свои ограниченные ресурсы для чего-то полезного, - это не мне. И благодарность за нахождение этой лазейки должна достаться Уве Шустеру.
Показать ещё 2 комментария
11

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

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

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

  • 0
    Помощники класса / записи, поля и методы разблокированы. Так просто.
9

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

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

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

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

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

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

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

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

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

  • 10
    Я думаю , EMBA все ли очень плохую услугу в закрытии этой опции, толкая людей в не обновление до последней версии, мне кажется контрпродуктивным.
  • 1
    Помощники класса могут все еще взломать классы в Сиэтле и ниже ........
Показать ещё 15 комментариев
4

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

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

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

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

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.
  • 2
    да, это будет работать в 10.1, однако это просто недосмотр и, как таковой, он может быть "исправлен" (неработоспособен) в следующей версии.
  • 0
    Кроме того, это связано с ценой за предотвращение работы этого кода в 64-битной Delphi.
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню