Search code examples
delphiassemblydelphi-2010basm

Call Object Method using ASM -- Part 2


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!


Solution

  • 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;