Ce este safecall?

Lucrez la crearea unui ActiveX EXE folosind VB6, și singurul exemplu pe care l-am primit este scris în Delphi.

Citind exemplul de cod, am observat că există anumite funcții ale căror semnături sunt urmate de cuvântul cheie safecall . Iată un exemplu:

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

Care este scopul acestui cuvânt cheie?

0
adăugat autor Greg Hewgill, sursa

4 răspunsuri

În COM, fiecare metodă este o funcție care returnează HRESULT :

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

Aceasta este o regulă absolută în COM:

  • nu există excepții în COM
  • totul returnează un HRESULT
  • HRESULT negativ indică o eroare
  • în limbile de nivel superior, eșecurile sunt mapate la excepții

Intenția designerilor COM a fost că limbile de nivel superior ar traduce automat metode Failed într-o excepție.

În limba dvs., invocarea COM va fi reprezentată fără HRESULT. De exemplu.:

  • Delphi-like: function AddSymbol(ASymbol: OleVariant): WordBool;
  • C#-like: WordBool AddSymbol(OleVariant ASymbol);

În Delphi puteți alege să utilizați semnătura funcției brute:

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

Și să rezolvați singuri ridicarea excepțiilor:

bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;

hr := thingy.AddSymbol('Seven', {out}bAdded);
if Failed(hr) then
    OleError(hr);

sau echivalentul mai scurt:

bAdded: WordBool;
thingy: IThingy;
hr: HRESULT;

hr := thingy.AddSymbol('Seven', {out}bAdded);
OleCheck(hr);

sau echivalentul mai scurt:

bAdded: WordBool;
thingy: IThingy;

OleCheck(thingy.AddSymbol('Seven'), {out}bAdded);

COM nu intenționa să te ocupi de HRESULT

Dar puteți să îi cereți lui Delphi să ascundă plumbul de la tine, ca să poți continua programarea:

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

În spatele scenelor, compilatorul va verifica în continuare returnarea HRESULT și va arunca o excepție EOleSysError în cazul în care HRESULT indică o eroare (adică era negativă). Versiunea safecall generată de compilatoare este echivalentă în mod funcțional cu:

function AddSymbol(ASymbol: OleVariant): WordBool; safecall;
var
   hr: HRESULT;
begin
   hr := AddSymbol(ASymbol, {out}Result);
   OleCheck(hr);
end;

Dar vă eliberează pur și simplu să sunați:

bAdded: WordBool;
thingy: IThingy;

bAdded := thingy.AddSymbol('Seven');

tl; dr: Puteți utiliza fie:

function AddSymbol(ASymbol: OleVariant; out RetValue: WordBool): HRESULT; stdcall;
function AddSymbol(ASymbol: OleVariant): WordBool; safecall;

Dar cel dintâi vă cere să vă ocupați de HRESULT de fiecare dată.

Bonus Chatter

Aproape niciodată nu vreți să vă ocupați de personalul HRESULT; se aglomerează programul cu zgomot care nu adaugă nimic. Dar, uneori, ați putea dori să verificați personalul HRESULT (de exemplu, doriți să rezolvați un eșec care nu este foarte excepțional). Niciodată versiunile Delphi nu au inclus interfețe traduse ale interfeței Windows care sunt declarate în ambele sensuri:

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;

sau din sursa 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;

Sufixul SC reprezintă safecall . Ambele interfețe sunt echivalente și puteți alege care să vă declare variabila COM în funcție de dorința dvs.:

//thingy: IThingy;
thingy: IThingySC;

Puteți chiar să aruncați între ele:

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;

Bonus pentru extra bonus - C #

C # implicit face echivalentul Delphi safecall , cu excepția C #:

  • trebuie să renunțați la maparea safecall
  • mai degrabă decât opțiunea de înscriere

În C#, declarați interfața COM ca:

[ComImport]
[Guid("{357D8D61-0504-446F-BE13-4A3BBE699B05}")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IThingy
{
   WordBool AddSymbol(OleVariant ASymbol);
   WordBool SubtractSymbol(OleVariant ASymbol);
}

Veți observa că COM HRESULT este ascunsă de dvs. Compilatorul C#, la fel ca și compilatorul Delphi, va verifica automat HRESULT-ul returnat și va arunca o excepție pentru dvs.

Și în C#, ca și în Delphi, puteți alege să vă ocupați personal de 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);
}

The [PreserveSig] tells the compiler to preserve the method signature exactly as is:

Indică dacă metodele nesupravegheate care au valorile returnate direct sau HRESULT sau retvalul valorile returnate sunt convertite automat în excepții.

0
adăugat

Safecall trece parametrii de la dreapta la stânga, în loc de pascal sau registru (implicit) de la stânga la dreapta

Cu safecall, procedeul sau funcția elimină parametrii din stack atunci când se întorc (cum ar fi pascal, dar nu ca cdecl în cazul în care este de până la apelant)

Safecall implementează excepția firewall-urilor; esp pe Win32, aceasta implementează interprocesarea notificării de eroare COM. Altfel ar fi identic cu stdcall (cealaltă convenție de asteptare folosită cu api câștigătoare)

0
adăugat

În plus, firewall-urile de excepție funcționează apelând SetErrorInfo() cu un obiect care acceptă IErrorInfo, astfel încât apelantul să poată obține informații extinse despre excepție. Acest lucru se face prin suprascrierea TObject.SafeCallException în ambele TComObject și TAutoIntfObject. Ambele tipuri implementează, de asemenea, ISupportErrorInfo pentru a marca acest fapt.

În cazul unei excepții, apelantul metodei safecall poate interoga ISupportErrorInfo, apoi interogare pentru interfața a cărei metodă a dus la o eroare HRESULT (set de biți înalți) și dacă returnează S_OK, GetErrorInfo() poate obține informațiile despre excepție (descriere, ajutor etc.), sub forma implementării IErrorInfo care a fost trecut la SetErrorInfo() de către Delphi RTL în suprascrierile SafeCallException) .

0
adăugat

What Francois said and if it wasn't for safecall your COM method call would have looked like below and you would have to do your own error checking instead of getting exceptions.

function AddSymbol(ASymbol: OleVariant; out Result: WordBool): HResult; stdcall;
0
adăugat