什么是安全的?

时间:2008-09-18 19:28:53

标签: delphi calling-convention

我正在使用VB6创建一个ActiveX EXE,我得到的唯一例子都是用Delphi编写的。

阅读示例代码,我注意到有一些函数的签名后跟 safecall 关键字。这是一个例子:

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

此关键字的目的是什么?

4 个答案:

答案 0 :(得分:14)

Safecall从左到右传递参数,而不是从左到右传递pascal或寄存器(默认)

使用safecall,过程或函数在返回时从堆栈中删除参数(如pascal,但不像cdecl那样由调用者决定)

Safecall实现异常“防火墙”; esp on Win32,这实现了进程间COM错误通知。否则它将与stdcall(与win api一起使用的另一个调用约定)相同

答案 1 :(得分:14)

此外,异常防火墙通过使用支持IErrorInfo的对象调用SetErrorInfo()来工作,以便调用者可以获得有关异常的扩展信息。这是通过TComObject和TAutoIntfObject中的TObject.SafeCallException覆盖来完成的。这两种类型都实现了ISupportErrorInfo来标记这一事实。

如果发生异常,safecall方法的调用者可以查询ISupportErrorInfo,然后查询其方法导致HRESULT(高位设置)失败的接口,如果返回S_OK,GetErrorInfo()可以以SafeCallException覆盖中Delphi RTL传递给SetErrorInfo()的IErrorInfo实现的形式获取异常信息(描述,帮助等)。

答案 2 :(得分:3)

What Francois said如果不是为了安全调用你的COM方法调用将如下所示,你将不得不进行自己的错误检查,而不是获得异常。

function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;

答案 3 :(得分:2)

在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#-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并不打算让你处理HRESULTs

但你可以让德尔福隐瞒你的管道,这样你就可以继续编程:

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

在幕后,编译器仍将检查返回HRESULT,如果HRESULT指示失败(即为负),则抛出EOleSysError异常。编译器生成的 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。

Bonus Chatter

你几乎不想自己处理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;

你甚至可以在他们之间施放:

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对您隐藏。与Delphi编译器一样,C#编译器会自动检查返回的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 返回值会自动转换为异常。