SafeCall Directive
The safecall
directive tells the compiler to use
the safe calling convention for the function or procedure.
Like stdcall
, the caller pushes the arguments onto
the stack, starting with the rightmost argument. Before the
subroutine returns, it pops the arguments from the stack.
If the routine is a function, Delphi passes an extra argument to
store its return value. Functions and procedures are converted
internally to functions that return an HResult
value. If the subroutine is a method, Self
and the
hidden function result parameter are last, so they are pushed first
onto the stack.
The compiler automatically wraps the subroutine body in an exception
handler, so Delphi catches all exceptions, and the
safecall
method never raises an exception that is
visible to the caller. If the subroutine returns normally, Delphi
stores zero in the hidden HResult
return value. If
the safecall
routine is a method that raises an
exception, Delphi calls the object’s
SafeCallException
method to map the exception to
an HResult
value. If the
safecall
routine is a plain function or procedure,
Delphi maps every exception to E_Unexpected
.
Schematically, calling a safecall
routine looks
like the following:
type TSomething = class function Divide(I, J: Integer): Integer; safecall; end; // If you write Divide as follows: function TSomething.Divide(I, J: Integer): Integer; begin Result := I div J; end; // Delphi compiles it into something that looks like this: function TSomething.Divide(I, J: Integer; var Rtn: Integer): HResult; begin try Rtn := I div J; Result := S_OK; except Result := Self.SafeCallException(ExceptObject, ExceptAddr); end; end;
The compiler generates the subroutine call using the
stdcall
calling convention, and also checks the
HResult
that the function returns. If the status
indicates an error, Delphi calls the procedure stored in
SafeCallErrorProc
. In other words, calling a
safecall
routine looks like the following:
// If you write this: X := Something.Divide(10, 2); // Delphi actually calls the method this way: ErrorCode := Something.Divide(X, 10, 2)); if Failed(ErrorCode) then begin if SafeCallErrorProc <> nil then SafeCallErrorProc(ErrorCode, EIP); // EIP=instruction pointer ReportError(24); end;