2016-04-19 6 views
27

В 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; 

Previously I would use a class helper to break open the base class.

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

В Delphi 10.1 Берлин, класс хелперы больше не имеют доступа к закрытым членам класса объекта или записи.

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

+7

Ничего себе. Это действительно отстой. Я думаю, что в прошлом я использовал только крекеры класса, чтобы исправить или расширить сломанные или недостающие объекты RTL/VCL. Это никогда не бывает изящным делом, но в качестве временного решения, пока Emba не исправляет свою кодовую базу, они были весьма ценными. Кажется сумасшедшим, что они будут калечить функцию, которая поможет нам управлять их различными ненадежными библиотеками фреймов ... –

+1

@Johan Это единственный вопрос, отмеченный 'delphi-10-berlin' - просто удалите его и дайте ему умереть. –

+2

@Johan Вам нужно как минимум 5 баллов в мастер-теге, чтобы предложить синоним. Это первый вопрос для основного тега, поэтому никто не может делать синонимы. Лучше всего просто удалить неправильный тег и справиться с ним, если и когда он станет проблемой. Если никто не использует его, это не проблема. –

ответ

20

Если удлиняется 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's answer охватывает хак, который позволяет доступ к частному методу для классов без расширенного RTTI.

Working with RTTI

+0

Попробуйте 'M: = TRttiContext.Create.GetType (TCustomForm) .GetMethod ('SetWindowState');' где 'M' является' TRttiMethod'. Вы найдете это 'nil'. Очевидно, что не все классы VCL/RTL/FMX расширили RTTI. –

+0

Невозможно получить доступ к защищенным/приватным методам в RTL/FMX/VCL с RTTI. Он управляется директивой [{$ RTTI}] (http://docwiki.embarcadero.com/RADStudio/en/RTTI_directive_ (Delphi)), которая является локальной по объему. –

+0

@RudyVelthuis Спасибо за исправление. Я не знаю, как на Земле мне удалось пропустить это. Библиотеки Core Delphi имеют приватную/защищенную RTTI, сгенерированную только для полей, а не для методов. –

9

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

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

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

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

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

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

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

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

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

+10

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

+1

Помощники класса могут все еще трещать классы в Сиэтле и ниже ........ –

+0

@Johan: это была ошибка, и она была исправлена. Это исправление оказало плохую услугу тем, кто хочет иметь легкий доступ к вещам, к которым у них не должно быть доступа вообще. –

4

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

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

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

Update (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, однако это просто надзор и как таковой, вероятно, будет «исправлен» (сломан) в следующем выпуске , – Johan

+0

Также это связано с ценой предотвращения работы этого кода в 64-битном Delphi. –

+0

@WarrenP простой ifdef и еще одна сборка x64 исправят это, но ja не улучшится. – Johan

18

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

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 для доступа охраняемых/частных методов

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

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

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

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

+6

@ RudyVelthuis. Трудно понять, почему Эмба считает, что класс-помощник должен быть ограничен, имея полный доступ к классу. Вспомогательный класс - это определение самого класса. Конечно, они могут делать то, что хотят, но выделяют эту функцию, когда они могут использовать свои ограниченные ресурсы для чего-то полезного, а не вне меня. И слава для того, чтобы найти эту лазейку, должна отправиться в Уве Шустер. –

+0

Это совсем не сложно. Инкапсуляция является одним из основных принципов ООП. То, что его ломает, легко подрывает это и может считаться ошибкой. И помощник класса НЕ является определением самого класса. Напротив. Это просто синтаксическое расширение. –

10

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

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

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

+0

Развернутые помощники, поля и методы записи класса/записи. Так просто. –

Смежные вопросы