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