¿Cómo acceder a métodos privados sin ayudantes?

En Delphi 10 Seattle podría usar el siguiente código para evitar restricciones de visibilidad demasiado estrictas.

¿Cómo obtengo acceso a variables privadas?

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

¿Y cómo obtengo acceso a métodos privados simples o virtuales?

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

Anteriormente usaba un ayudante de clase para abrir la clase base.

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

En Delphi 10.1 Berlin, los ayudantes de clase ya no tienen acceso a miembros privados de la clase de materia o registro.

¿Hay alguna forma alternativa de acceder a miembros privados?

Si hay información RTTI extendida generada para los miembros privados de la clase, campos y / o métodos, puede usarla para obtener acceso a ellos.

Por supuesto, el acceso a través de RTTI es mucho más lento de lo que era a través de los ayudantes de clase.

Accediendo a los métodos:

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

Acceso a variables:

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

La información RTTI predeterminada generada para las clases RTL / VCL / FMX está siguiendo

  • Campos – private , protected , public , published
  • Métodos – public , published
  • Propiedades – public , published

Desafortunadamente, eso significa que no se puede acceder a los métodos privados a través de RTTI para las bibliotecas centrales de Delphi. La respuesta de @LU RD cubre hack que permite el acceso privado a métodos para clases sin RTTI extendido.

Trabajando con RTTI

Todavía hay una manera de utilizar class helpers para el acceso de métodos privados en Delphi 10.1 Berlín:

 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; 

Desafortunadamente, no hay forma de acceder a campos privados / privados estrictos por ayudantes de clase con Delphi 10.1 Berlin. RTTI es una opción, pero se puede considerar lento si el rendimiento es crítico.

Aquí hay una manera de definir el desplazamiento a un campo en el inicio utilizando ayudantes de clase y 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; 

Esto tendrá la ventaja de que la parte RTTI lenta solo se ejecuta una vez.


Nota: Usar RTTI para acceder a métodos protegidos / privados

El RTL / VCL / FMX no ha declarado la visibilidad para el acceso de métodos protegidos / privados con RTTI. Debe configurarse con la directiva local {$ RTTI} .

El uso de RTTI para acceder a métodos privados / protegidos en otro código requiere, por ejemplo, la configuración:

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

Si desea una forma limpia que no afecte el rendimiento, aún puede acceder a campos privados desde un asistente de registro utilizando la instrucción with.

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

Espero que mantengan este método abierto, porque tenemos un código con demandas de alto rendimiento.

Suponiendo que RTTI extendido no está disponible, entonces sin recurrir a lo que se consideraría piratería, no puede acceder a miembros privados desde el código en una unidad diferente. Por supuesto, si RTTI está disponible, se puede usar.

Tengo entendido que la capacidad de descifrar miembros privados utilizando ayudantes fue un accidente involuntario. La intención es que los miembros privados solo sean visibles desde el código en la misma unidad, y los miembros privados estrictos solo sean visibles desde el código en la misma clase. Este cambio corrige el accidente.

Sin la capacidad de hacer que el comstackdor rompa la clase por ti, necesitarás recurrir a otras formas de hacerlo. Por ejemplo, podría volver a declarar lo suficiente de la clase TBase para poder engañar al comstackdor y decirle dónde vivía un miembro.

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

Ahora puedes escribir

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

Pero esto es horrendamente frágil y se romperá tan pronto como se TBase el diseño de TBase .

Eso funcionará para los miembros de datos, pero para los métodos no virtuales, probablemente necesite usar técnicas de desensamblaje en tiempo de ejecución para encontrar la ubicación del código. Para miembros virtuales, esta técnica se puede usar para encontrar el desplazamiento de VMT.

Otras lecturas:

Si no necesita soporte para el comstackdor ARM , puede encontrar otra solución aquí .

Con un ensamblador en línea, puede acceder al campo o método privado, fácilmente.

Creo que la respuesta de David es mejor en la mayoría de los casos, pero si necesita una solución rápida para una gran clase, este método podría ser más útil.

Actualización (17 de junio): Acabo de notar que olvidé compartir su código de muestra para acceder a los campos privados de su publicación . lo siento.

 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; 

Aquí está su código de muestra para llamar al método privado .

 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.