Inline Assembler in Delphi (VI)
Calling external procedures
By Ernesto De Spirito <
edspirito@latiumsoftware.com>
API functions and the Stdcall calling convention
API functions are called transparently from
inline assembler with the CALL statement. However, we must take into account that passing parameters to API functions is different since they normally use the Stdcall
calling convention, instead of the Register
calling convention, which is the one we've seen so far since it is the default convention.
In the Stdcall
calling convention, all parameters are passed on the stack, from right to left, i.e. the last (rightmost) parameter is pushed first, and the first (leftmost) parameter is pushed last, so it'll the one on top of the stack. Here is an example of a
procedure that
calls an API function:
procedure HideForm(Handle: THandle);
// Windows.ShowWindow(Handle, SW_HIDE);
asm
push SW_HIDE // push 0 // Pass second parameter
push Handle // push eax // Pass first parameter
call Windows.ShowWindow // Call the API ShowWindow
end;
If we have to call a method that uses the stdcall convention, remember that the Self pointer is an invisible first parameter, so it'll be pushed last on the stack.
If we have to write functions that use the stdcall convention, there's nothing special we should worry about. The compiler will always create a stack frame and references to parameter names will be converted into addresses relative to the base pointer:
function AddAndMultiply(i1, i2, i3: integer): integer; stdcall;
asm // ==> push ebp; mov ebp, esp
// Result := (i1 + i2) * i3;
mov eax, i1 // mov eax, [ebp+8]
add eax, i2 // add eax, [ebp+12]
imul i3 // imul [ebp+16]
end; // ==> pop ebp; ret 12
This is a sample call for the function:
asm
// a := AddAndMultiply(1, 2, 3); // result should be 9
push 3
push 2
push 1
call AddAndMultiply
mov a, eax
end;
After entering the function, the stack would look like this:
| |
+----------------+
| Old EBP | [EBP], [ESP]
+----------------+
| Return Address | [EBP+4]
+----------------+
| i1 = 1 | [EBP+8]
+----------------+
| i2 = 2 | [EBP+12]
+----------------+
| i3 = 3 | [EBP+16]
+----------------+
| |
C/C++ libraries and the Cdecl calling convention
Sometimes we need to access functions in object files (.OBJ), static libraries (.LIB) or dynamic libraries (.DLL) written in C or C++, and quite frequently these functions use the Cdecl
calling convention. It is very much like the Stdcall convention, but the stack should be cleaned by the caller, i.e., the caller should pop the parameters it pushed, or -better- increment the stack pointer.
function AddAndMultiply(i1, i2, i3: integer): integer; cdecl;
asm // ==> push ebp; mov ebp, esp
// Result := (i1 + i2) * i3;
mov eax, i1 // mov eax, [ebp+8]
add eax, i2 // add eax, [ebp+12]
imul i3 // imul [ebp+16]
end; // ==> pop ebp; ret
Notice in the comment for the last line that the function doesn't move the stack pointer as it did in the previous example that used the Stdcall convention, so the caller is the one who is responsible for that. This is a sample call for the function:
asm
// a := AddAndMultiply(1, 2, 3); // should be 9
push 3
push 2
push 1
call AddAndMultiply
add esp, 12 // clean the stack
mov a, eax
end;
Notice that if the parameters were of type Byte instead of Integer, we should still move the stack pointer by 12 bytes since each parameter would take 32 bits (4 bytes) anyway.
The Pascal calling convention
Many C/C++ programmers prefer the Pascal
calling convention over the Cdecl
calling convention because it is more compact and also faster since the called function clears the stack in the RET statement, as it happens in the Stdcall convention. The Pascal convention is like the Stdcall convention, but parameters are passed left-to-right instead of right-to-left, i.e. the first (leftmost) parameter is pushed first, and the last (rightmost) parameter is pushed last:
function AddAndMultiply(i1, i2, i3: integer): integer; pascal;
asm // ==> push ebp; mov ebp, esp
// Result := (i1 + i2) * i3;
mov eax, i1 // mov eax, [ebp+16]
add eax, i2 // add eax, [ebp+12]
imul i3 // imul [ebp+8]
end; // ==> pop ebp; ret 12
Notice how the addresses of the parameters are translated different than in the previous examples.
This is a sample call for the function:
asm
// a := AddAndMultiply(1, 2, 3); // should be 9
push 1
push 2
push 3
call AddAndMultiply
mov a, eax
end;
After entering the function, the stack would look like this:
| |
+----------------+
| EBP | [EBP], [ESP]
+----------------+
| Return Address | [EBP+4]
+----------------+
| i3 = 3 | [EBP+8]
+----------------+
| i2 = 2 | [EBP+12]
+----------------+
| i1 = 1 | [EBP+16]
+----------------+
| |
Previous: Inline Assembler in Delphi (V) - Objects Next: Inline Assembler in Delphi (VII) - 128-bit integer arithmetic