我正在使用VB6创建ActiveX EXE,但是我所得到的唯一示例都是用Delphi编写的。
阅读示例代码时,我注意到有一些函数在其签名后面跟着safecall关键字。以下是一个示例:
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
这个关键字的目的是什么?
我正在使用VB6创建ActiveX EXE,但是我所得到的唯一示例都是用Delphi编写的。
阅读示例代码时,我注意到有一些函数在其签名后面跟着safecall关键字。以下是一个示例:
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
这个关键字的目的是什么?
Safecall将参数从右往左传递,而不是像Pascal或寄存器(默认)那样从左到右传递。
使用safecall时,过程或函数在返回时将参数从堆栈中删除(类似于Pascal,但不像Cdecl那样由调用方负责)。
Safecall实现了异常“防火墙”;特别是在Win32上,这实现了进程间COM错误通知。否则,它与stdcall相同(win api使用的另一种调用约定)。
HRESULT
的函数:IThingy = interface
['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;
COM中有一个绝对的规则:
COM设计者的意图是让高级语言自动将Failed方法转换成异常。
因此,在您自己的语言中,COM调用将被表示为不带HRESULT的形式。例如:
function AddSymbol(ASymbol: OleVariant): WordBool;
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);
但是你可以要求Delphi将这些细节隐藏起来,这样你就可以专注于编程:
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');
简而言之:您可以使用以下任一选项:
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;
C#默认情况下执行与Delphi的safecall相等操作,不同之处在于:
在C#中,你可以将COM接口声明为:
[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
WordBool AddSymbol(OleVariant ASymbol);
WordBool SubtractSymbol(OleVariant ASymbol);
}
[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返回值时,这些返回值是否直接转换,还是自动转换为异常。弗朗索瓦所说的,如果没有safecall,你的COM方法调用将会像下面这样,并且你需要自己进行错误检查而不是获取异常。
function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;