Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's safecall?

I'm working on the creation of an ActiveX EXE using VB6, and the only example I got is all written in Delphi.

Reading the example code, I noticed there are some functions whose signatures are followed by the safecall keyword. Here's an example:

function AddSymbol(ASymbol: OleVariant): WordBool; safecall;

What is the purpose of this keyword?

like image 324
Mario Marinato Avatar asked Sep 18 '08 19:09

Mario Marinato


4 Answers

Safecall passes parameters from right to left, instead of the pascal or register (default) from left to right

With safecall, the procedure or function removes parameters from the stack upon returning (like pascal, but not like cdecl where it's up to the caller)

Safecall implements exception 'firewalls'; esp on Win32, this implements interprocess COM error notification. It would otherwise be identical to stdcall (the other calling convention used with the win api)

like image 91
Francesca Avatar answered Oct 06 '22 11:10

Francesca


Additionally, the exception firewalls work by calling SetErrorInfo() with an object that supports IErrorInfo, so that the caller can get extended information about the exception. This is done by the TObject.SafeCallException override in both TComObject and TAutoIntfObject. Both of these types also implement ISupportErrorInfo to mark this fact.

In the event of an exception, the safecall method's caller can query for ISupportErrorInfo, then query that for the interface whose method resulted in a failure HRESULT (high bit set), and if that returns S_OK, GetErrorInfo() can get the exception info (description, help, etc., in the form of the IErrorInfo implementation that was passed to SetErrorInfo() by the Delphi RTL in the SafeCallException overrides).

like image 43
Barry Kelly Avatar answered Oct 06 '22 11:10

Barry Kelly


In COM, every method is a function that returns an HRESULT:

IThingy = interface
   ['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
   function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;

This is an absolute rule in COM:

  • there are no exceptions in COM
  • everything returns an HRESULT
  • negative HRESULT indicates a failure
  • in higher level languages, failures are mapped to exceptions

It was the intention of the COM designers that higher level languages would automatically translate Failed methods into an exception.

So in your own language, the COM invocation would be represented without the HRESULT. E.g.:

  • Delphi-like: function AddSymbol(ASymbol: OleVariant): WordBool;
  • C#-like: WordBool AddSymbol(OleVariant ASymbol);

In Delphi you can choose to use the raw function signature:

IThingy = interface
   ['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
   function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;

And handle the raising of exceptions yourself:

bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;

hr := thingy.AddSymbol('Seven', {out}bAdded);
if Failed(hr) then
    OleError(hr);

or the shorter equivalent:

bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;

hr := thingy.AddSymbol('Seven', {out}bAdded);
OleCheck(hr);

or the shorter equivalent:

bAdded: WordBool;
thingy: IThingy;

OleCheck(thingy.AddSymbol('Seven'), {out}bAdded);

COM didn't intend for you to deal with HRESULTs

But you can ask Delphi to hide that plumbing away from you, so you can get on with the programming:

IThingy = interface
   ['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
   function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
end;

Behind the scenes, the compiler will still check the return HRESULT, and throw an EOleSysError exception if the HRESULT indicated a failure (i.e. was negative). The compiler-generated safecall version is functionally equivalent to:

function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
var
   hr: HRESULT;
begin
   hr := AddSymbol(ASymbol, {out}Result);
   OleCheck(hr);
end;

But it frees you to simply call:

bAdded: WordBool;
thingy: IThingy;

bAdded := thingy.AddSymbol('Seven');

tl;dr: You can use either:

function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;

But the former requires you to handle the HRESULTs every time.

Bonus Chatter

You almost never want to handle the HRESULTs yourself; it clutters up the program with noise that adds nothing. But sometimes you might want to check the HRESULT yourself (e.g. you want to handle a failure that isn't very exceptional). Never versions of Delphi have starting included translated Windows header interfaces that are declared both ways:

IThingy = interface
   ['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
   function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;

IThingySC = interface
   ['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
   function AddSymbol(ASymbol: OleVariant): WordBool); safecall;
end;

or from the RTL source:

  ITransaction = interface(IUnknown)
    ['{0FB15084-AF41-11CE-BD2B-204C4F4F5020}']
    function Commit(fRetaining: BOOL; grfTC: UINT; grfRM: UINT): HResult; stdcall;
    function Abort(pboidReason: PBOID; fRetaining: BOOL; fAsync: BOOL): HResult; stdcall;
    function GetTransactionInfo(out pinfo: XACTTRANSINFO): HResult; stdcall;
  end;

  { Safecall Version }
  ITransactionSC = interface(IUnknown)
    ['{0FB15084-AF41-11CE-BD2B-204C4F4F5020}']
    procedure Commit(fRetaining: BOOL; grfTC: UINT; grfRM: UINT); safecall;
    procedure Abort(pboidReason: PBOID; fRetaining: BOOL; fAsync: BOOL); safecall;
    procedure GetTransactionInfo(out pinfo: XACTTRANSINFO); safecall;
  end;

The SC suffix stands for safecall. Both interfaces are equivalent, and you can choose which to declare your COM variable as depending on your desire:

//thingy: IThingy;
thingy: IThingySC;

You can even cast between them:

thingy: IThingSC;
bAdded: WordBool;

thingy := CreateOleObject('Supercool.Thingy') as TThingySC;

if Failed(IThingy(thingy).AddSymbol('Seven', {out}bAdded) then
begin
   //Couldn't seven? No sixty-nine for you
   thingy.SubtractSymbol('Sixty-nine');
end;

Extra Bonus Chatter - C#

C# by default does the equivalent of Delphi safecall, except in C#:

  • you have to opt-out of safecall mapping
  • rather than opt-in

In C# you would declare your COM interface as:

[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
   WordBool AddSymbol(OleVariant ASymbol);
   WordBool SubtractSymbol(OleVariant ASymbol);
}

You'll notice that the COM HRESULT is hidden from you. The C# compiler, like the Delphi compiler, will automatically check the returned HRESULT and throw an exception for you.

And in C#, as in Delphi, you can choose to handle the HRESULTs yourself:

[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
   [PreserveSig]
   HRESULT AddSymbol(OleVariant ASymbol, out WordBool RetValue);

   WordBool SubtractSymbol(OleVariant ASymbol);
}

The [PreserveSig] tells the compiler to preserve the method signature exactly as is:

Indicates whether unmanaged methods that have HRESULT or retval return values are directly translated or whether HRESULT or retval return values are automatically converted to exceptions.

like image 30
Ian Boyd Avatar answered Oct 06 '22 10:10

Ian Boyd


What Francois said and if it wasn't for safecall your COM method call would have looked like below and you would have to do your own error checking instead of getting exceptions.

function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;
like image 26
Lars Truijens Avatar answered Oct 06 '22 12:10

Lars Truijens