This question is based on a previous, but that's just FYI.
I've managed to get it working, however, I've found something that's not clear to me, so if anyone can explain the following behaviour, it would be awesome.
I have the following class:
type
TMyObj = class
published
procedure testex(const s: string; const i: integer);
end;
procedure TMyObj.testex(const s: string; const i: integer);
begin
ShowMessage(s + IntToStr(i));
end;
and the following two procedures:
procedure CallObjMethWorking(AMethod: TMethod; const AStrValue: string; const AIntValue: Integer);
begin
asm
PUSH DWORD PTR AIntValue;
PUSH DWORD PTR AStrValue;
CALL AMethod.Code;
end;
end;
procedure CallObjMethNOTWorking(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer);
begin
asm
MOV EAX, AInstance;
PUSH DWORD PTR AIntValue;
PUSH DWORD PTR AStrValue;
CALL ACode;
end;
end;
In order to test the working version, one needs to call the following:
procedure ...;
var
LObj: TMyObj;
LMethod: TMethod;
LStrVal: string;
LIntVal: Integer;
begin
LObj := TMyObj.Create;
try
LMethod.Data := Pointer( LObj );
LMethod.Code := LObj.MethodAddress('testex');
LStrVal := 'The year is:' + sLineBreak;
LIntVal := 2012;
CallObjMethWorking(LMethod, LStrVal, LIntVal);
finally
LObj.Free;
end; // tryf
end;
and in order to test the NOT working version:
procedure ...;
var
LObj: TMyObj;
LCode: Pointer;
LData: Pointer;
LStrVal: string;
LIntVal: Integer;
begin
LObj := TMyObj.Create;
try
LData := Pointer( LObj );
LCode := LObj.MethodAddress('testex');
LStrVal := 'The year is:' + sLineBreak;
LIntVal := 2012;
CallObjMethNOTWorking(LData, LCode, LStrVal, LIntVal);
finally
LObj.Free;
end; // tryf
end;
And finally the question: why isn't CallObjMethNOTWorking working, while CallObjMethWorking is? I'm guessing that there's something special in how the compiler treats TMethod... but since my assembly knowledge is limited, I can't understand it.
I would highly appreciate if someone could explain this to me, thank you!
Default calling convention in Delphi Win32 is "register". The first parameter is passed in EAX, the second in EDX and the third in ECX. The stack is only used if there are more than three parameters, or if value types larger than 4 bytes are passed, but that is not the case in your example.
Your first CallObjMethWorking procedure works because the compiler has already placed aStrValue in EDX and aIntValue in ECX when CallObjMethWorking was called. However, since you are not cleaning up your two push instructions, bad things are bound to happen when the procedure returns.
Your code should look like this. The stdcall directive is optional in this case, but it might be a good idea to use it for things like this to make sure your parameters aren't lost because you use the registers for other purposes before you get around to actually call the method:
procedure CallObjMeth(AInstance, ACode: Pointer; const AStrValue: string; const AIntValue: Integer); stdcall;
asm
MOV EAX, AInstance;
MOV EDX, DWORD PTR AStrValue;
MOV ECX DWORD PTR AIntValue;
CALL ACode;
end;