In COM, every method is a function that returns an HRESULT
:
IThingy = interface
['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;
This is an absolute rule in COM:
- there are no exceptions in COM
- everything returns an HRESULT
- negative HRESULT indicates a failure
- in higher level languages, failures are mapped to exceptions
It was the intention of the COM designers that higher level languages would automatically translate Failed methods into an exception.
So in your own language, the COM invocation would be represented without the HRESULT. E.g.:
- Delphi-like:
function AddSymbol(ASymbol: OleVariant): WordBool;
- C#-like:
WordBool AddSymbol(OleVariant ASymbol);
In Delphi you can choose to use the raw function signature:
IThingy = interface
['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
end;
And handle the raising of exceptions yourself:
bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;
hr := thingy.AddSymbol('Seven', {out}bAdded);
if Failed(hr) then
OleError(hr);
or the shorter equivalent:
bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;
hr := thingy.AddSymbol('Seven', {out}bAdded);
OleCheck(hr);
or the shorter equivalent:
bAdded: WordBool;
thingy: IThingy;
OleCheck(thingy.AddSymbol('Seven'), {out}bAdded);
COM didn't intend for you to deal with HRESULTs
But you can ask Delphi to hide that plumbing away from you, so you can get on with the programming:
IThingy = interface
['{357D8D61-0504-446F-BE13-4A3BBE699B05}']
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
end;
Behind the scenes, the compiler will still check the return HRESULT, and throw an EOleSysError
exception if the HRESULT indicated a failure (i.e. was negative). The compiler-generated safecall version is functionally equivalent to:
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
var
hr: HRESULT;
begin
hr := AddSymbol(ASymbol, {out}Result);
OleCheck(hr);
end;
But it frees you to simply call:
bAdded: WordBool;
thingy: IThingy;
bAdded := thingy.AddSymbol('Seven');
tl;dr: You can use either:
function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
But the former requires you to handle the HRESULTs every time.
Bonus Chatter
You almost never want to handle the HRESULTs yourself; it clutters up the program with noise that adds nothing. But sometimes you might want to check the HRESULT yourself (e.g. you want to handle a failure that isn't very exceptional). Never versions of Delphi have starting included translated Windows header interfaces that are declared both ways:
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;
or from the RTL source:
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;
The SC suffix stands for safecall. Both interfaces are equivalent, and you can choose which to declare your COM variable as depending on your desire:
//thingy: IThingy;
thingy: IThingySC;
You can even cast between them:
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 Bonus Chatter - C#
C# by default does the equivalent of Delphi safecall, except in C#:
- you have to opt-out of safecall mapping
- rather than opt-in
In C# you would declare your COM interface as:
[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
WordBool AddSymbol(OleVariant ASymbol);
WordBool SubtractSymbol(OleVariant ASymbol);
}
You'll notice that the COM HRESULT
is hidden from you. The C# compiler, like the Delphi compiler, will automatically check the returned HRESULT and throw an exception for you.
And in C#, as in Delphi, you can choose to handle the HRESULTs yourself:
[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:
Indicates whether unmanaged methods that have HRESULT or retval return values are directly translated or whether HRESULT or retval return values are automatically converted to exceptions.