Что такое safecall?

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

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

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

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

0
добавлено отредактировано
Просмотры: 28
<< a href = "http://en.wikipedia.org/wiki/X86_calling_conventions#safecall>" rel = "nofollow noreferrer"> ru.wikipedia.org/wiki/X86_calling_conventions#safecall> ;
добавлено автор Greg Hewgill, источник

4 ответы

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

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

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

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

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

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

  • Delphi-like: function AddSymbol(ASymbol: OleVariant): WordBool;
  • C#-like: 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;

Extra Бонусная болтовня - C#

C # по умолчанию имеет эквивалент Delphi safecall , за исключением 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);
}

Вы заметите, что 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);
}

The [PreserveSig] tells the compiler to preserve the method signature exactly as is:

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

0
добавлено

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

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

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

0
добавлено

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

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

0
добавлено

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
добавлено