Вопрос

Я работаю над созданием 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);
}

Тот Самый [Консервация] сообщает компилятору сохранить сигнатуру метода в точном виде:

Указывает, являются ли неуправляемые методы, которые имеют РЕЗУЛЬТАТ или ретвал возвращаемые значения преобразуются непосредственно или независимо от того, РЕЗУЛЬТАТ или ретвал возвращаемые значения автоматически преобразуются в исключения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top