Что safecall?

Я работаю над созданием ActiveX EXE с помощью VB6, и единственный пример, который я получил, написан на Delphi.

читать пример кода, я заметил, что есть некоторые функции, подписи которых следуют safecall ключевое слово. Вот пример:

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

какова цель этого сайта?

4 ответов


Safecall передает параметры справа налево, вместо pascal или register (по умолчанию) слева направо

с safecall процедура или функция удаляет параметры из стека при возвращении (например, pascal, но не как cdecl, где это зависит от вызывающего абонента)

Safecall реализует исключение "брандмауэры"; esp на Win32, это реализует уведомление об ошибке INTERPROCESS COM. В противном случае он будет идентичен stdcall (используется другое соглашение о вызовах с помощью api win)


кроме того, брандмауэры исключений работают, вызывая SetErrorInfo() с объектом, поддерживающим IErrorInfo, чтобы вызывающий объект мог получить расширенную информацию об исключении. Это делается с помощью TObject.Переопределение SafeCallException в TComObject и TAutoIntfObject. Оба этих типа также реализуют ISupportErrorInfo, чтобы отметить этот факт.

в случае исключения вызывающий метод safecall может запросить ISupportErrorInfo, а затем запросить это для интерфейса чей метод привел к сбою HRESULT (high bit set), и если это возвращает S_OK,GetErrorInfo() может получить информацию об исключении (описание, справка и т. д., в виде реализации IErrorInfo, которая была передана в SetErrorInfo() по Delphi RTL в safecallexception переопределяет).


что сказал Франсуа и если бы не safecall, ваш вызов метода COM выглядел бы так, как показано ниже, и вам пришлось бы выполнять собственную проверку ошибок вместо получения исключений.

function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;

в COM каждый метод является функцией, которая возвращает HRESULT:

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

это абсолютное правило в COM:

  • в COM
  • все возвращает HRESULT
  • отрицательный HRESULT указывает на сбой
  • в языках более высокого уровня, сбои отображаются для исключения

намерение дизайнеров COM состояло в том, что языки более высокого уровня будут автоматически перевести ошибка методы в исключение.

таким образом, на вашем родном языке вызов COM будет представлен без HRESULT. Например:

  • Делфи-как: function AddSymbol(ASymbol: OleVariant): WordBool;
  • C# - like: WordBool AddSymbol(OleVariant ASymbol);

в Delphi вы можете использовать подпись функции raw:

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

и обрабатывать повышение исключений самостоятельно:

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

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

или более короткий эквивалент:

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

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

или более короткий эквивалент:

bAdded: WordBool;
thingy: IThingy;

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

COM не хотел, чтобы вы имели дело с HRESULTs

но вы можете попросить Delphi скрыть от вас эту сантехнику, чтобы вы могли продолжить Программирование:

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

за кулисами компилятор все равно проверит возврат HRESULT и бросит EOleSysError исключение, если HRESULT указал на сбой (т. е. был отрицательным). Сгенерированный компилятором safecall версия функционально эквивалентна:

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

но это освобождает вас просто звоните:

bAdded: WordBool;
thingy: IThingy;

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

tl; dr: вы можете использовать либо:

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

но первый требует, чтобы вы каждый раз обрабатывали HRESULTs.

Бонус Треп

вы почти никогда не хотите обрабатывать HRESULTs самостоятельно; он загромождает программу шумом, который ничего не добавляет. Но иногда вы можете проверить HRESULT самостоятельно (например, вы хотите справиться с неудачей, которая не является исключительной). Никогда версии Delphi не запускали включенные переведенные интерфейсы заголовков Windows, которые объявляются в обоих направлениях:

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;

или от источника 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;

на SC суффикс означает safecall. Оба интерфейса эквивалентны, и вы можете выбрать, какую переменную COM объявить в зависимости от вашего желания:

//thingy: IThingy;
thingy: IThingySC;

вы можете даже бросить между ними:--21-->

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;

Дополнительный Бонус Болтовня-C#

C# по умолчанию делает эквивалент Delphi safecall, за исключением C#:

  • вы должны отказаться от safecall mapping
  • а не opt-in

в C# вы объявите свой COM-интерфейс как:

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

вы заметите, что COM HRESULT скрывается от вас. Компилятор C#, как и компилятор Delphi, будет автоматически проверьте возвращенный HRESULT и выдайте исключение для вас.

и в C#, как и в Delphi, вы можете самостоятельно обрабатывать 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);
}

на [PreserveSig] говорит компилятору сохранить подпись метода точно так же, как есть:

указывает, являются ли неуправляемые методы, которые имеют значение HRESULT или код возвращаемые значения переводятся напрямую или значение HRESULT или код возвращаемые значения автоматически преобразуются в исключения.