¡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
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
public
Procedure Proc1; Virtual;
Procedure Proc2;
end;
TF2 = Class (TForm1)
Procedure Proc1; Override;
End;
var
Form1: TForm1;
implementation
{$R *.dfm}
Procedure TForm1.Proc1;
Begin
ShowMessage ('Executing Proc1');
End;
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
Proc1;
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;
VirtualProtect (Address, SizeOf (Pointer), Page_ReadWrite, Protection);
Try
PPointer (Address)^ := @TForm1.Proc2;
Finally
VirtualProtect (Address, SizeOf (Pointer), Protection, @Dummy);
End;
Proc1;
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
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
public
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
M.Data := Self;
M.Code := @TForm1.Proc1;
TM (M); 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.