¿Qué es safecall?

Estoy trabajando en la creación de un EXE de ActiveX usando VB6, y el único ejemplo que obtuve está escrito en Delphi.

Al leer el código de ejemplo, noté que hay algunas funciones cuyas firmas son seguidas por la palabra clave safecall . Aquí hay un ejemplo:

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

¿Cuál es el propósito de esta palabra clave?

0
agregado editado
Puntos de vista: 4
<< a href = "http://en.wikipedia.org/wiki/X86_calling_conventions#safecall>" rel = "nofollow noreferrer"> en.wikipedia.org/wiki/X86_calling_conventions#safecall> ;
agregado el autor Greg Hewgill, fuente

4 Respuestas

En COM, cada método es una función que devuelve un HRESULT :

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

Esta es una regla absoluta en COM:

  • no hay excepciones en COM
  • todo devuelve un HRESULT
  • HRESULT negativo indica una falla
  • en idiomas de nivel superior, las fallas se asignan a excepciones

Los diseñadores de COM tenían la intención de que los lenguajes de nivel superior tradujeran automáticamente los métodos Fallidos a una excepción.

Entonces, en su propio idioma, la invocación COM estaría representada sin HRESULT. P.ej.:

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

En Delphi puedes elegir usar la firma de la función raw:

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

Y maneje la generación de excepciones usted mismo:

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

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

o el equivalente más corto:

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

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

o el equivalente más corto:

bAdded: WordBool;
thingy: IThingy;

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

COM no tenía la intención de que se ocupe de HRESULT

Pero puedes pedirle a Delphi que te oculte esa plomería, para que puedas seguir con la programación:

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

Detrás de escena, el compilador seguirá comprobando el HRESULT devuelto y lanzará una excepción EOleSysError si HRESULT indicó una falla (es decir, fue negativa). La versión safecall generada por el compilador es funcionalmente equivalente a:

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

Pero te permite simplemente llamar:

bAdded: WordBool;
thingy: IThingy;

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

tl; dr: puede usar cualquiera de los siguientes:

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

Pero el primero requiere que manejes los HRESULT cada vez.

Bonus Chatter

Casi nunca desea manejar los HRESULT usted mismo; atormenta el programa con ruido que no agrega nada. Pero a veces es posible que desee verificar el HRESULT usted mismo (por ejemplo, si desea manejar una falla que no es muy excepcional). Nunca las versiones de Delphi han comenzado a incluir interfaces de encabezado de Windows traducidas que se declaran en ambos sentidos:

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;

o de la fuente RTL:

  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;

El sufijo SC significa safecall . Ambas interfaces son equivalentes, y puede elegir cuál declarar su variable COM según su deseo:

//thingy: IThingy;
thingy: IThingySC;

Incluso puedes lanzar entre ellos:

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;

Bono de bonificación adicional - C #

C # por defecto hace el equivalente a Delphi safecall , excepto en C #:

  • tienes que inhabilitar el mapeo safecall
  • en lugar de habilitarla

En C# declararías tu interfaz COM como:

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

Notará que el código COM HRESULT está oculto para usted. El compilador de C#, como el compilador Delphi, verificará automáticamente el HRESULT devuelto y generará una excepción para usted.

Y en C#, como en Delphi, puede elegir manejar los HRESULT usted mismo:

[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:

Indica si los métodos no administrados que tienen valores de retorno HRESULT o retval se traducen directamente o si HRESULT o retval los valores de retorno se convierten automáticamente en excepciones.

0
agregado

Safecall pasa parámetros de derecha a izquierda, en lugar del pascal o registro (predeterminado) de izquierda a derecha

Con safecall, el procedimiento o función elimina los parámetros de la pila al regresar (como pascal, pero no como cdecl, donde depende de la persona que llama)

Safecall implementa excepciones 'firewalls'; esp en Win32, esto implementa la notificación de error COM entre procesos. De lo contrario, sería idéntico a stdcall (la otra convención de llamadas utilizada con la API ganadora)

0
agregado

Además, los cortafuegos de excepción funcionan llamando a SetErrorInfo() con un objeto que admita IErrorInfo, para que la persona que llama pueda obtener información extendida sobre la excepción. Esto se realiza mediante la anulación de TObject.SafeCallException en TComObject y TAutoIntfObject. Ambos tipos también implementan ISupportErrorInfo para marcar este hecho.

En el caso de una excepción, el llamante del método safecall puede consultar ISupportErrorInfo y luego consultarla para la interfaz cuyo método resultó en un error HRESULT (alto bit set), y si eso devuelve S_OK, GetErrorInfo() puede obtener la información de excepción (descripción, ayuda, etc., en la forma de la implementación IErrorInfo que fue pasado a SetErrorInfo() por Delphi RTL en las anulaciones de SafeCallException) .

0
agregado

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;
0
agregado