O que é safecall?

Eu estou trabalhando na criação de um EXE ActiveX usando VB6, e o único exemplo que tenho é todo escrito em Delphi.

Lendo o código de exemplo, notei que existem algumas funções cujas assinaturas são seguidas pela palavra-chave safecall . Aqui está um exemplo:

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

Qual é o objetivo dessa palavra-chave?

0
adicionado editado
Visualizações: 5
<< a href = "http://en.wikipedia.org/wiki/X86_calling_conventions#safecall>" rel = "nofollow noreferrer"> en.wikipedia.org/wiki/X86_calling_conventions#safecall> ;
adicionado o autor Greg Hewgill, fonte

4 Respostas

Em COM, todo método é uma função que retorna um HRESULT :

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

Esta é uma regra absoluta no COM:

  • não há exceções em COM
  • tudo retorna um HRESULT
  • HRESULT negativo indica uma falha
  • em idiomas de nível superior, as falhas são mapeadas para exceções

A intenção dos designers de COM era que as linguagens de nível superior traduzissem automaticamente os métodos Falha em uma exceção.

Portanto, em seu próprio idioma, a chamada COM seria representada sem o HRESULT. Por exemplo.:

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

No Delphi você pode escolher usar a assinatura da função raw:

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

E lide com o aumento de exceções:

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

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

ou o equivalente mais curto:

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

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

ou o equivalente mais curto:

bAdded: WordBool;
thingy: IThingy;

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

COM não pretendia que você lidasse com HRESULTs

Mas você pode pedir ao Delphi para esconder esse encanamento longe de você, então você pode continuar com a programação:

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

Nos bastidores, o compilador ainda verificará o retorno HRESULT e lançará uma exceção EOleSysError se o HRESULT indicou uma falha (ou seja, foi negativo). A versão safecall gerada pelo compilador é funcionalmente equivalente a:

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

Mas liberta-o para simplesmente ligar:

bAdded: WordBool;
thingy: IThingy;

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

tl; dr: você pode usar:

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

Mas o primeiro exige que você manipule os HRESULTs todas as vezes.

Chatter de bônus

Você quase nunca quer lidar com os HRESULTs sozinho; ele atravessa o programa com ruído que não acrescenta nada. Mas às vezes você pode querer verificar o HRESULT você mesmo (por exemplo, você quer lidar com uma falha que não é muito excepcional). Nunca as versões do Delphi começaram a incluir as interfaces de cabeçalho traduzidas do Windows declaradas nos dois 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;

ou da fonte 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;

O sufixo SC significa safecall . Ambas as interfaces são equivalentes, e você pode escolher qual declarar sua variável COM como dependendo do seu desejo:

//thingy: IThingy;
thingy: IThingySC;

Você pode até mesmo conjurar entre eles:

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 Chatter de bônus - C#

C # por padrão faz o equivalente a safecall do Delphi, exceto em C #:

  • você precisa desativar o mapeamento de segurança
  • em vez de aceitar

Em C# você declararia sua interface COM como:

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

Você notará que o COM HRESULT está oculto de você. O compilador C#, como o compilador Delphi, irá verificar automaticamente o HRESULT retornado e lançar uma exceção para você.

E em C#, como em Delphi, você pode escolher manipular os HRESULTs:

[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 se os métodos não gerenciados que possuem valores de retorno HRESULT ou retval são traduzidos diretamente ou se HRESULT ou retval os valores de retorno são automaticamente convertidos em exceções.

0
adicionado

Safecall passa parâmetros da direita para a esquerda, em vez do pascal ou registra (padrão) da esquerda para a direita

Com safecall, o procedimento ou função remove parâmetros da pilha ao retornar (como o pascal, mas não como o cdecl, onde cabe ao chamador)

A Safecall implementa exceções 'firewalls'; esp em Win32, isso implementa notificação de erro COM interprocess. Caso contrário, seria idêntico ao stdcall (a outra convenção de chamada usada com o win api)

0
adicionado

Além disso, os firewalls de exceção funcionam chamando SetErrorInfo() com um objeto que suporta IErrorInfo, para que o chamador possa obter informações estendidas sobre a exceção. Isso é feito pela substituição TObject.SafeCallException em TComObject e TAutoIntfObject. Esses dois tipos também implementam ISupportErrorInfo para marcar esse fato.

No caso de uma exceção, o chamador do método safecall pode consultar ISupportErrorInfo e, em seguida, consultá-lo para a interface cujo método resultou em uma falha HRESULT (conjunto de bits alto) e, se retornar S_OK, GetErrorInfo() pode obter as informações de exceção (descrição, ajuda, etc., na forma da implementação IErrorInfo que foi passado para SetErrorInfo() pelo Delphi RTL nas substituições do SafeCallException) .

0
adicionado

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
adicionado