Что такое безопасный звонок?
-
01-07-2019 - |
Вопрос
Я работаю над созданием EXE-файла ActiveX с использованием VB6, и единственный пример, который у меня есть, написан на Delphi.
Читая пример кода, я заметил, что есть некоторые функции, за сигнатурами которых следуют безопасный вызов ключевое слово.Вот пример:
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
Какова цель этого ключевого слова?
Решение
Safecall передает параметры справа налево, вместо pascal или register (по умолчанию) слева направо
С safecall процедура или функция удаляет параметры из стека при возврате (как в pascal, но не как в cdecl, где это зависит от вызывающего объекта)
Safecall реализует "брандмауэры" с исключениями;особенно в Win32, это реализует уведомление о межпроцессной COM-ошибке.В противном случае он был бы идентичен stdcall (другому соглашению о вызовах, используемому с win api).
Другие советы
Кроме того, брандмауэры исключений работают путем вызова SetErrorInfo() с объектом, поддерживающим IErrorInfo, так что вызывающий может получить расширенную информацию об исключении.Это делается с помощью переопределения исключения TObject.SafeCallException как в TComObject, так и в TAutoIntfObject.Оба этих типа также реализуют ISupportErrorInfo, чтобы отметить этот факт.
В случае исключения вызывающий метод safecall может запросить ISupportErrorInfo, затем запросить его для интерфейса, метод которого привел к сбою HRESULT (установлен высокий бит), и если это возвращает 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
- отрицательный результат указывает на сбой
- в языках более высокого уровня сбои сопоставляются с исключениями
Разработчики COM предполагали, что языки более высокого уровня будут автоматически переводиться Потерпел неудачу методы превращаются в исключение.
Таким образом, на вашем родном языке вызов COM был бы представлен без HRESULT .Например.:
- Похожий на 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 не предполагал, что вы будете иметь дело с HRESULTs
Но вы можете попросить Delphi скрыть от вас эту сантехнику, чтобы вы могли продолжить программирование:
IThingy = interface
['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
function AddSymbol(ASymbol: OleVariant): WordBool); safecall;
end;
За кулисами компилятор по-прежнему будет проверять возвращаемый HRESULT и выдавать EOleSysError
исключение, если HRESULT указал на сбой (т. е.был отрицательным).Сгенерированный компилятором безопасный вызов версия функционально эквивалентна:
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;
Но первое требует, чтобы вы каждый раз обрабатывали результаты.
Бонусная Болтовня
Вам почти никогда не захочется справляться с результатами самостоятельно;это загромождает программу шумом, который ничего не добавляет.Но иногда вы можете захотеть проверить результат самостоятельно (напримервы хотите справиться с отказом, который не является чем-то исключительным).Никогда версии 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 суффикс расшифровывается как безопасный вызов.Оба интерфейса эквивалентны, и вы можете выбрать, как объявить вашу 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;
Дополнительная Бонусная болтовня - C#
C # по умолчанию выполняет эквивалент Delphi безопасный вызов, за исключением C#:
- вы должны отказаться от сопоставления safecall
- вместо того, чтобы подписываться
В C # вы бы объявили свой COM-интерфейс как:
[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
WordBool AddSymbol(OleVariant ASymbol);
WordBool SubtractSymbol(OleVariant ASymbol);
}
Вы заметите, что КОМ HRESULT
это скрыто от вас.Компилятор C #, как и компилятор Delphi, автоматически проверит возвращенный HRESULT и выдаст для вас исключение.
И в C #, как и в Delphi, вы можете самостоятельно обрабатывать результаты:
[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);
}
Тот Самый [Консервация] сообщает компилятору сохранить сигнатуру метода в точном виде:
Указывает, являются ли неуправляемые методы, которые имеют РЕЗУЛЬТАТ или ретвал возвращаемые значения преобразуются непосредственно или независимо от того, РЕЗУЛЬТАТ или ретвал возвращаемые значения автоматически преобразуются в исключения.