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

阅读示例代码,我注意到有一些函数的签名后面跟着 安全呼叫 关键词。这是一个例子:

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

这个关键字的目的是什么?

有帮助吗?

解决方案

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

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

Safecall 实现异常“防火墙”;特别是在 Win32 上,这实现了进程间 COM 错误通知。否则它与 stdcall 相同(与 win api 一起使用的其他调用约定)

其他提示

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

如果发生异常,safecall 方法的调用者可以查询 ISupportErrorInfo,然后查询其方法导致失败的接口 HRESULT(高位设置),如果返回 S_OK, 获取错误信息() 可以获取异常信息(描述、帮助等,以传递给的 IErrorInfo 实现的形式) 设置错误信息() 由 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#: 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 指示失败(即为负数)。编译器生成的 安全呼叫 版本在功能上等同于:

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');

长话短说:您可以使用:

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 后缀代表 安全呼叫. 。这两个接口是等效的,您可以根据您的需要选择声明 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# 中:

  • 您必须选择退出安全呼叫映射
  • 而不是选择加入

在 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);
}

[保留签名] 告诉编译器按原样保留方法签名:

指示非托管方法是否具有 H结果 或者 雷特瓦尔 返回值是直接翻译还是 H结果 或者 雷特瓦尔 返回值会自动转换为异常。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top