Ver Mensaje Individual
  #5  
Antiguo 02-02-2009
Avatar de Al González
[Al González] Al González is offline
In .pas since 1991
 
Registrado: may 2003
Posts: 5.604
Reputación: 30
Al González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en brutoAl González Es un diamante en bruto
¡Hola Poyo!

Viendo este hilo de Aeff, me vino a la mente que podría existir una manera de sustituir un método virtual por otro método, para casos como ese, donde estando ya un vasto árbol de clases definido, la derivación de clases sería una solución poco efectiva (si el compilador soportara herencia insertada, otro gallo cantaría ).

Entonces recordé que habías mencionado algo sobre sustituir entradas de la VMT:
Cita:
Empezado por poyo Ver Mensaje
Siguiendo con la experimentación, querer reemplazar el puntero de un método virtual de la VMT de la siguiente manera:

vmt^.UserDefinedVirtuals[x] := @TForm1.MyMethod;

me di cuenta de que no se podía por algo... ARROJABA UNA EXCEPCION!
Tras de mirar y mirar si estaba haciendo algo mal, caí en que los Administradores de Memoria (memory managers) de los sistemas operativos que trabajan con microprocesadores que operan en modo protegido, marcan a las páginas de memoria con atributos especiales haciendo que estas se puedan (o no) leer, escribir y/o ejecutar. Claro que todo esto depende del microprocesador y/o sistema operativo... en la actualidad creo que ya todos los microsprocesadores lo soportan y el windows lo viene soportando desde hace rato... no tengo idea.

La cuestión es que implementé una función que se encarga de desproteger la vmt y otra que vuelve a protegerla, para dejarla todo como estaba... bueno, casi
Con esa premisa y habiendo encontrado este código: http://www.koders.com/delphi/fid7782...px?s=algorithm, que parece ser el mismo que amablemente anexaste en el mensaje anterior, realicé esta prueba:
Código Delphi [-]
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Procedure Proc1; Virtual;
    Procedure Proc2;
  end;

  TF2 = Class (TForm1)
    Procedure Proc1; Override;
  End;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Método virtual a sustituir
Procedure TForm1.Proc1;
Begin
  ShowMessage ('Executing Proc1');
End;

// Método que reemplazará a Proc1
Procedure TForm1.Proc2;
Begin
  ShowMessage ('Executing Proc2!');
End;

Procedure TF2.Proc1;
Begin
  Inherited Proc1;
End;

procedure TForm1.Button1Click(Sender: TObject);
Var
  Address :Pointer;
  Dummy : DWord;
  Protection : DWord;
begin
  { TEMA: Cómo cambiar una entrada de la VMT de una clase, para que al
    realizarse una llamada a un método virtual, sea otro el método
    ejecutado. }

  Proc1;  // Esta sentencia llama a Proc1 (como es de suponerse)

  { Obtenemos la dirección de memoria que tiene la entrada del método
    virtual Proc1 en la VMT de la clase TForm1 }
  Asm
    Mov EAX, Self
    Mov EAX, [EAX]  // Dirección de la clase (VMT) del formulario (Self)
    Add EAX, VMTOffset TForm1.Proc1  // Desplazamiento de la entrada Proc1
    Mov Address, EAX
  End;

  { Desprotegemos los primeros cuatro bytes que hay a partir de esa
    dirección de memoria para poder cambiarlos }
  VirtualProtect (Address, SizeOf (Pointer), Page_ReadWrite, Protection);

  Try
    { Hacemos que la entrada de la VMT apunte ahora al método Proc2
      (sustitución de Proc1 por Proc2) }
    PPointer (Address)^ := @TForm1.Proc2;
  Finally
    { Restauramos el nivel de protección de la región de memoria
      modificada }
    VirtualProtect (Address, SizeOf (Pointer), Protection, @Dummy);
  End;

  Proc1;  // ¡Esta sentencia llama a Proc2!

  { PERO: En este caso no es efectivo el cambio porque la sentencia
    "Inherited", en el interior de TF2.Proc1, es una llamada resuelta en
    tiempo de compilación. }
  With TF2.Create (Nil) Do
    Try
      Proc1;
    Finally
      Free;
    End;

  Close;
end;

Seguramente ya habrás hecho algo similar. Pero, como podrás apreciar en el ejemplo, el principal problema es que no todas las llamadas a métodos virtuales son late binding, es decir, aquellas donde el programa determina en tiempo de ejecución a qué rutina va a saltar revisando primero de qué clase es el objeto.

¿Cuáles llamadas a métodos virtuales son resueltas en tiempo de compilación (realizadas "estáticamente")? Particularmente las que usan la palabra reservada Inherited, y también las que se llevan a cabo mediante variables procedimentales.

Entonces, creo que la utilidad práctica de sustituir una entrada VMT de método virtual sería aplicable, básicamente, a aquellos casos donde ya existen varias clases derivadas de la clase que implementa el método a sustituir, pero ninguna de esas clases descendientes ha redefinido dicho método, o, si alguna lo ha hecho, no usa Inherited o no nos interesa que para ella se ejecute el código original (y, claro está, no queremos o no podemos modificar el código fuente de la clase ancestro en cuestión).


Cita:
Empezado por poyo Ver Mensaje
No entiendo bien lo que planteas aquí.
A ver si comprendo: una entrada de una función virtual en una VMT, si esta es abtracta, apuntará a _AbstractError. Cada Método Abstracto tiene un dirección de memoria particular (corresponfiende al Offset dentro de un array de punteros a métodos, no?).
La pregunta es por qué hay un elementos en la el array que no están implementados? (es decir, apuntan a _AbstractError)?
Eso fue referente a mi observación de que cada método virtual, aunque sea abstracto, tiene su propio código máquina, su propia dirección de memoria. La única instrucción relevante que contiene el código máquina de un método abstracto es un salto a la función _AbstractError. En la ventana CPU del depurador podrás observar la instrucción "jmp @AbstractError", si dentro de ella haces el seguimiento de este código:

Código Delphi [-]
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Procedure Proc1; Virtual; Abstract;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Type
  TM = Procedure Of Object;
Var
  M :TMethod;
begin
//  Proc1;  // Llamada habitual

  M.Data := Self;
  M.Code := @TForm1.Proc1;
  TM (M);  // Llamada con variable procedimental
end;

El camino que seguirá el programa es distinto, dependiendo de si el enlace de llamada al método virtual es estático o tardío (late binding). Con Inherited y variables procedimentales, saltará a la dirección del método abstracto sin consultar la VMT; mientras que con una llamada tipo "Objeto.Método" sí leerá la entrada respectiva de la VMT. ¿Interesante no?


En cuanto a la función GetVirtualMethodCount, aún no me queda claro en qué se basa ésta para determinar que ha encontrado el fin de la VMT. El código, aún con comentarios, me resulta un poco confuso. Insisto en que tiene que haber una especie de marca o dato indicativo seguro en el cual pueda basarse la función para saber dónde termina la lista de entradas de la VMT, puesto que el tamaño de cada VMT es variable y, que yo sepa, no hay ningún lugar donde esté señalado el tamaño que tiene. O bien una regla de almacenamiento que no estoy percibiendo...

Saludos.

Al González.
Responder Con Cita