Что за звонок? - PullRequest
       78

Что за звонок?

16 голосов
/ 18 сентября 2008

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

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

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

Какова цель этого ключевого слова?

Ответы [ 4 ]

14 голосов
/ 19 сентября 2008

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

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

14 голосов
/ 18 сентября 2008

Safecall передает параметры справа налево вместо паскаля или регистра (по умолчанию) слева направо

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

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

3 голосов
/ 19 сентября 2008

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

function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;
2 голосов
/ 16 мая 2018

В 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. E.g.:

  • Delphi-подобные : function AddSymbol(ASymbol: OleVariant): WordBool;
  • C # -подобный : WordBool AddSymbol(OleVariant ASymbol);

В Delphi вы можете использовать необработанную сигнатуру функции:

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 не собирался иметь дело с HRESULT

Но вы можете попросить 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;

Но первый требует, чтобы вы обращались с HRESULT каждый раз.

Бонусная болтовня

Вы почти никогда не хотите сами справляться с HRESULT; это загромождает программу шумом, который ничего не добавляет. Но иногда вы можете захотеть проверить 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;

Вы даже можете разыграть между ними:

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;

Дополнительный бонус Chatter - C #

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

  • Вы должны отказаться от картирования безопасного вызова
  • вместо подписки

В 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, вы можете самостоятельно обрабатывать HRESULT:

[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 или retval возвращаемыми значениями или же HRESULT или retval возвращаются автоматически преобразуется в исключения.

...