Хорошо, я наконец-то понял, как это сделать.
Pascal vmts немного путают.
Он использует 4 вида vmts:
* Один используется только для опубликованных методов
* One используется только RTTI, содержащий дополнительные данные для всех методов
* Один используемых сообщений и динамических методов
* И тот, используется, когда вы просто вызываете ObjectMethod
Это заняло много времени назад, но теперь это работает.
Для тех из вас, интересно, как его сделать, у меня есть:
program DynClass;
uses windows;
type
// ***
// * Most of these types I got from "http://hallvards.blogspot.de/2006/04/hack-9-dynamic-method-table-structure.html"
// ***
PClass = ^TClass;
TDMTIndex = Smallint;
PDmtIndices = ^TDmtIndices;
TDmtIndices = array[0..High(Word)-1] of TDMTIndex;
PDmtMethods = ^TDmtMethods;
TDmtMethods = array[0..High(Word)-1] of Pointer;
PDmt = ^TDmt;
TDmt = packed record
Count: word;
Indicies: TDmtIndices; // really [0..Count-1]
Methods : TDmtMethods; // really [0..Count-1]
end;
PVmtMethodEntry = ^TVmtMethodEntry;
TVmtMethodEntry = packed record
Len: Word;
CodeAddress: Pointer;
Name: ShortString;
{Tail: TVmtMethodEntryTail;} // only exists if Len indicates data here
end;
PVmtMethodEntryEx = ^TVmtMethodEntryEx;
TVmtMethodEntryEx = packed record
Entry: PVmtMethodEntry;
Flags: Word;
VirtualIndex: Smallint; // signed word
end;
PEquals = function (Self,Obj: TObject): Boolean;
PGetHashCode = function (Self: TObject): Integer;
PToString = function (Self: TObject): string;
PSafeCallException = function (Self: TObject; ExceptObject: TObject; ExceptAddr: Pointer): HResult;
PAfterConstruction = procedure (Self: TObject);
PBeforeDestruction = procedure (Self: TObject);
PDispatch = procedure (Self: TObject; var Message);
PDefaultHandler = procedure (Self: TObject; var Message);
PNewInstance = function (Self: TClass) : TObject;
PFreeInstance = procedure (Self: TObject);
PDestroy = procedure (Self: TObject; OuterMost: ShortInt);
PVmt = ^TVmt;
TVmt = packed record
SelfPtr : TClass;
IntfTable : Pointer;
AutoTable : Pointer;
InitTable : Pointer;
TypeInfo : Pointer;
FieldTable : Pointer;
MethodTable : Pointer;
DynamicTable : PDmt;
ClassName : PShortString;
InstanceSize : PLongint;
Parent : PClass;
Equals : PEquals; // these I had to add they might
GetHashCode : PGetHashCode; // be incorrect for older delphi
ToString : PToString; // versions (this works for XE2)
SafeCallException : PSafeCallException;
AfterConstruction : PAfterConstruction;
BeforeDestruction : PBeforeDestruction;
Dispatch : PDispatch;
DefaultHandler : PDefaultHandler;
NewInstance : PNewInstance;
FreeInstance : PFreeInstance;
Destroy : PDestroy;
{UserDefinedVirtuals: array[0..999] of procedure;}
end;
// v taked from System.Rtti
function GetBitField(Value, Shift, Bits: Integer): Integer;
begin Result := (Value shr Shift) and ((1 shl Bits) - 1); end;
// v substituted from System.Rtti
function GetIsDynamic(handle: PVmtMethodEntryEx): boolean;
begin case GetBitField(Handle.Flags,3,2) of
2,3: result := true;
else result := false; end; end;
// a method that can be used to write data into protected RAM
function hackWrite(const addr: PPointer; const value: Pointer): boolean;
var RestoreProtection, Ignore: DWORD; begin
if VirtualProtect(addr,SizeOf(addr^),PAGE_EXECUTE_READWRITE,RestoreProtection) then begin
addr^ := Value; result := true;
VirtualProtect(addr,SizeOf(addr^),RestoreProtection,Ignore);
FlushInstructionCache(GetCurrentProcess,addr,SizeOf(addr^)); // flush cache
end else result := false; end;
// the Vmt is located infront of a Class
function GetVmt(AClass: TClass): PVmt;
begin Result := PVmt(AClass); Dec(PVmt(Result)); end;
// seares the vmt for
function getVirtualIndex(vmt: PVmt; aMeth: shortString; out isDynamic: boolean): SmallInt;
var P: PByte;
procedure readClassic;
var count: PWord; meth: PVmtMethodEntry; next: PByte; I: integer; begin
Count := PWord(P); inc(PWord(P));
for I := 0 to pred(Count^) do begin
meth := PVmtMethodEntry(P);
if meth.Name=aMeth then
begin result := I; break; end;
inc(p,meth.Len);
end; end;
procedure readExtendedMethods;
var Count: PWord; I: integer; meth: PVmtMethodEntryEx; begin
Count := PWord(P); inc(PWord(P));
for i := 0 to pred(count^) do begin
meth := PVmtMethodEntryEx(P);
if meth.Entry.Name=aMeth then begin
result := meth.VirtualIndex;
isDynamic := GetIsDynamic(meth);
exit; end;
inc(PVmtMethodEntryEx(P));
end; end;
begin isDynamic := false;
P := vmt.MethodTable; result := low(SmallInt);
readClassic; // classic method are method declared in a published area
if result=low(SmallInt)
then readExtendedMethods; // extended methods were added in D2010, when Rtti was introduced
end;
procedure overwriteMethod(vmt: PVmt; vmtID: smallInt; isDynamic: boolean; meth: Pointer); overload;
var loc: PByte; dynIndex: word; i: smallInt;
begin if vmtID<>low(SmallInt) then begin
if isDynamic then begin
loc := @vmt.DynamicTable.Indicies[0]; // goto first index entry
for i := 0 to vmt.DynamicTable.Count-1 do begin
if vmt.DynamicTable.Indicies[i] = vmtId
then begin vmtId := i; break; end; end;
//^find the vmt id in the dynamic table
inc(loc,
(vmt.DynamicTable.Count*sizeOf(TDMTIndex))+ // end of indices
(vmtID*sizeOf(Pointer))); // desired method entry
end else begin
loc := PByte(vmt);
inc(PVmt(loc)); // skip to the end of the vmt (thats where all the methods are stored)
inc(loc,vmtID*sizeOf(Pointer)); // skip to the exact position of the method
end; end;
hackWrite(PPointer(loc),meth); // overwrite it
end;
procedure overwriteMethod(c: TClass; methName: shortString; meth: Pointer); overload;
var vmtID: smallInt; isDynamic: boolean; vmt: PVmt; begin
vmt := GetVmt(c);
vmtID := getVirtualIndex(vmt,methName,isDynamic);
overwriteMethod(vmt,vmtID,isDynamic,meth);
end;
// ** everything on needs for dynPascal is now defined
type TBaseTestClass = class(TObject)
public
procedure updateA; virtual; abstract;
procedure updateB; virtual; abstract;
end;
type TTestClass = class(TBaseTestClass)
public
procedure foobar; dynamic;
procedure updateA; override;
procedure updateB; override;
end;
type TTestClass2 = class(TTestClass)
public
procedure updateA; override;
procedure updateB; override;
end;
{ TTestClass }
procedure TTestClass.foobar; begin writeLn('foobar'); end;
procedure TTestClass.updateA; begin writeLn('TTestClass.updateA'); end;
procedure TTestClass.updateB; begin writeLn('TTestClass.updateB'); end;
{ TTestClass2 }
procedure TTestClass2.updateA; begin writeLn('TTestClass2.updateA'); end;
procedure TTestClass2.updateB; begin writeLn('TTestClass2.updateB'); end;
procedure testMeth(self: TObject);
begin writeLn('!!!!!!!!!!!!Overwritten method called!!!!!!!!!!!!'); end;
var fTable: PVmt;
var a,b: TObject;
var vmt: PVmt;
var I: integer; begin
fTable := GetVmt(TTestClass);
a := TTestClass.Create;
b := TTestClass2.Create;
// ** demonstration calls, to show that the types work normal at first
TBaseTestClass(a).updateA;
TBaseTestClass(b).updateA;
TBaseTestClass(a).updateB;
TBaseTestClass(b).updateB;
writeLn('');
// ** overwrite a few methods with testMeth and repeat the calling process
overwriteMethod(TTestClass,'foobar',addr(testMeth));
//^dynamic methods like foobar work differently but I included handles for those, too
overwriteMethod(TTestClass,'updateA',addr(testMeth));
overwriteMethod(TTestClass2,'updateA',addr(testMeth));
TTestClass(a).foobar;
TBaseTestClass(a).updateA;
TBaseTestClass(b).updateA;
TBaseTestClass(a).updateB; // These 2 methods I didn't overwrite
TBaseTestClass(b).updateB; // ...
readLn;
end.
Теперь вам просто нужно прокомментировать, какой компилятор и какая версия, так как это не будет универсальным –
Как я уже сказал: Delphi XE2. В соответствии с документацией, которая является компилятором версии 23 {$ IFDEF VER230}. Я хотел бы сделать это универсальным, но я только владею XE2. Если он не работает для вас, попробуйте изменить TVmt, чтобы соответствовать вашим смещениям vmt, описанным в системном блоке. – user3296587
Ах, есть ссылки XE2 где-то в этом источнике. Вы только отметили вопрос с Pascal, и обычно этот тег используется для Free Pascal/Lazarus, а не для Delphi. FPC, например. рассматривает динамические как виртуальные –