Entwickler-Ecke

Open Source Units - Init Once


delfiphan - So 29.08.10 00:35
Titel: Init Once
Das Singleton-Pattern thread-safe korrekt zu implementieren ist nicht ganz einfach. Es wird häufig entweder über eine CriticalSection oder über Double Checked Locking [http://en.wikipedia.org/wiki/Double-checked_locking] implementiert. CriticalSections sind für diesen Zweck sehr langsam und Double Checked Locking kann je nach Compiler Schwierigkeiten bereiten. Mit den neuen APIs von Vista+ kann ein thread-safe Singleton einfach implementiert werden. Genau dazu ist diese Unit da.

http://msdn.microsoft.com/en-us/library/ms683493(v=VS.85).aspx


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
unit InitializeOnce;

interface

type
  TInitializeOnce = class
  private
    FInit: Pointer;
    FOwnsObject: Boolean;
    function GetInstance: TObject;
  protected
    function DoInstantiate: TObject; virtualabstract;
  public
    destructor Destroy; override;
    property OwnsObject: Boolean read FOwnsObject write FOwnsObject;
    property Instance: TObject read GetInstance;
  end;

implementation

function InitOnceExecuteOnce(var InitOnce: Pointer; Callback: Pointer; Param: Pointer; var Context: Pointer): Boolean stdcallexternal 'kernel32.dll' name 'InitOnceExecuteOnce';

function InitHandleFunction(InitOnce: Pointer; Parameter: Pointer; var Context: Pointer): Boolean; stdcall;
begin
  Context := TInitializeOnce(Parameter).DoInstantiate;
  Result := True;
end;

{ TInitializeOnce }

destructor TInitializeOnce.Destroy;
begin
  if FOwnsObject and (FInit <> nilthen
    Instance.Free;
  inherited;
end;

function TInitializeOnce.GetInstance: TObject;
begin
  InitOnceExecuteOnce(FInit, @InitHandleFunction, Pointer(Self), Pointer(Result));
end;

end.


Marc. - So 29.08.10 16:08

Hi!

Delphi-Quelltext
 
26:
27:
28:
29:
30:
{ ... }
function InitHandleFunction(InitOnce: Pointer; Parameter: Pointer; var Context: Pointer): Boolean; stdcall;
begin
  Context := TInitializeOnce(Parameter).DoInstantiate;
  Result := True;
end;

Was genau bringt Result := True hier, außer den Compiler zu besänftigen, da die Funktion einen Rückgabewert erwartet?

Die Original-Funktion sieht, wenn ich das richtig recherchiert habe, so aus:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
BOOL CALLBACK InitHandleFunction (
    PINIT_ONCE InitOnce,
    PVOID Parameter,
    PVOID *lpContext)
{

  HANDLE hEvent;

  hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);

  if (NULL == hEvent)
  {
    return FALSE;
  }
  else
  {
    *lpContext = hEvent;
    return TRUE;
  }
}

Hier wird die Hilfsvariable hEvent zunächst auf NULL überprüft; je nach Ergebnis wird Context deren Wert anschließend zugewiesen.
Sollte es daher nicht in etwa wie folgt lauten:

Delphi-Quelltext
1:
2:
3:
4:
5:
function InitHandleFunction(InitOnce: Pointer; Parameter: Pointer; var Context: Pointer): Boolean; stdcall;
begin
  Context := TInitializeOnce(Parameter).DoInstantiate;
  Result := Context <> nil;
end;


Beste Grüße,
Marc


delfiphan - So 29.08.10 16:31

Hi! Jo, ginge auch, ist vielleicht sogar ein bisschen besser, danke.

Bei Delphi erwarte ich bei einem Fehler in einer Factorymethode eher eine Exception, daher wird immer True zurückgegeben (oder eben eine Exception)