В 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, помощники класса больше не имеют доступа к частным членам предмета или записи.
Есть ли альтернативный способ доступа к закрытым членам?
Если имеется расширенная информация 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.
M := TRttiContext.Create.GetType(TCustomForm).GetMethod('SetWindowState');
где M
является TRttiMethod
. Вы найдете это nil
. Очевидно, что не все классы VCL / RTL / FMX имеют расширенные сгенерированные 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
, чтобы обмануть компилятор, сообщив вам, где жил член.
type
THackBase = class(TObject)
private
FMemberVar: integer;
end;
Теперь вы можете написать
var
obj: TBase;
....
MemberVar := THackBase(obj).FMemberVar;
Но это ужасно хрупкое и сломается, как только будет изменен макет TBase
.
Это будет работать для членов данных, но для не виртуальных методов вам, вероятно, потребуется использовать методы разборки времени выполнения, чтобы найти местоположение кода. Для виртуальных членов этот метод можно использовать для поиска смещения VMT.
Дальнейшее чтение:
Если вам не нужна поддержка компилятора 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.
delphi-10-berlin
- просто удалите его и дайте ему умереть.