[Delphi/COM] COM server niet beëindigen als client stopt

Pagina: 1
Acties:

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 08:27

Tomatoman

Fulltime prutser

Topicstarter
In Delphi 7 heb ik applicatie geschreven, die ook kan worden aangeroepen als een out-of-process COM server. Ook heb ik een DLL geschreven die in de Windows shell geïnstalleerd wordt en die als enige taak heeft de applicatie te starten. Het werkt als volgt: de gebruiker kiest in de Windows Explorer in een contextmenu het menu-item dat door de DLL is toegevoegd. Zodra dat gebeurt start de DLL de COM server en geeft via de COM interface wat instructies aan de applicatie. Dat werkt allemaal zoals gepland.

Zodra de DLL echter klaar is met zijn commando's aan de COM server (dat duurt een fractie van een seconde), releast hij de COM server, wat ertoe leidt dat de applicatie automatisch wordt beëindigd. Dat is ongewenst. Ik wil dat de applicatie gewoon blijft draaien. Hoe doe ik dat?

Code in de DLL:
Delphi:
1
2
3
4
5
6
7
8
9
10
11
12
13
procedure RunApp(FileNames: TStrings);
var
  App: IRenamerApp;
  OleFileNames: IStrings;
begin
  App := CoRenamerApp.Create; // Applicatie (met user interface) wordt gestart

  { Wat informatie aan de applicatie doorgeven (verder niet relevant) }
  GetOleStrings(FileNames, OleFileNames);
  App.AddStrings(OleFileNames);

  { Nu er geen referentie naar App meer is, wordt de applicatie afgesloten. }
end;
Ik kan voorkomen dat de applicatie wordt afgesloten door _AddRef toe te voegen:
Delphi:
13
  App._AddRef;
maar dat geeft weer een ander probleem, namelijk dat de applicatie niet meer weet of hij veilig kan worden afgesloten. Standaard krijg je dan in Delphi-applicaties een waarschuwing dat de COM server nog wordt gebruikt door een of meer applicaties.

Eigenlijk wil ik een method implementeren die aan de applicatie de instructie geeft dat hij niet mag afsluiten als de IRenamerApp interface wordt gereleast. Bijvoorbeeld:
Delphi:
13
  App.KeepRunning;
Aan de kant van de COM server heb ik dit al geprobeerd:
Delphi:
1
2
3
4
5
6
7
type
  TComServerAccess = class(TComServer);

procedure TRenamerApp.KeepRunning;
begin
  TComServerAccess(Factory.ComServer).CountObject(False);
end;
Dit probeersel geeft slechts als resultaat dat de COM server onmiddellijk afgesloten wordt en dat is precies het omgekeerde van wat ik wil bereiken.

Nog een laatste opmerking: de COM server kan ook als een gewone executable worden gestart. Ook dan dient hij zich als een 'nette' COM server te gedragen: de gebruiker kan de applicatie pas afsluiten als geen enkele client meer verbonden is met de COM server.

Een goede grap mag vrienden kosten.


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 08:27

Tomatoman

Fulltime prutser

Topicstarter
Uiteindelijk heb ik de volgende oplossing gevonden, die naar behoren werkt:
Delphi:
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
44
45
46
47
48
49
50
51
52
53
54
55
interface

uses
  ComServ;

type
  TRenamerApp = class(TAutoObject, IRenamerApp)
  protected
    procedure KeepRunning; safecall;
  end;

  TRenamerAppFactory = class(TAutoObjectFactory)
  private
    FDontShutdown: Boolean;
    procedure SetDontShutdown(Value: Boolean);
    procedure ComServerLastRelease(var Shutdown: Boolean);
  public
    destructor Destroy; override;
    property DontShutdown: Boolean read FDontShutdown write SetDontShutdown;
  end;

implementation

procedure TRenamerApp.KeepRunning;
begin
  (Factory as TRenamerAppFactory).DontShutdown := True;
end;

{ TRenamerAppFactory }

procedure TRenamerAppFactory.SetDontShutdown(Value: Boolean);
begin
  if FDontShutdown <> Value then
  begin
    FDontShutdown := Value;
    ComServ.ComServer.OnLastRelease := ComServerLastRelease;
  end;
end;

procedure TRenamerAppFactory.ComServerLastRelease(var Shutdown: Boolean);
begin
  Shutdown := not FDontShutdown;
end;

destructor TRenamerAppFactory.Destroy;
begin
  if Assigned(ComServ.ComServer) then
    ComServ.ComServer.OnLastRelease := nil;
  inherited;
end;

initialization
  TRenamerAppFactory.Create(ComServer, TRenamerApp, Class_RenamerApp,
    ciMultiInstance, tmApartment);
end.
Het blijkt dat je de OnLastRelease event van het globale ComServer object kunt gebruiken om het shutdown-gedrag van de appliatie te beïnvloeden. Ik gebruik de TRenamerAppFactory class factory om de OnLastRelease event af te handelen, omdat de class factory ook blijft bestaan als de IRenamerApp interface al gereleast is. Dat voorkomt dat ComServer.OnLastRelease verwijst naar een method van een niet meer bestaand object.

Een goede grap mag vrienden kosten.


Verwijderd

Ja aardige oplossing maarrr niet geheel waterdicht. Wat bijvoorbeeld als explorer.exe crasht?

Wat jij wilt komt vaak voor. Denk bijvoorbeeld aan een Windows service die altijd draait en waar COM clients verbinding mee maken. Deze starten ook geen nieuwe service op. Het enige verschil is dat de service al draait en jouw applicatie niet.

Dus je start host applicatie met ShellExecute en laat je hem registreren in de ROT (Running Object Table). Je kunt dan met GetActiveObject een interface krijgen naar de draaiende applicatie. Wanneer je deze interface releast blijft je applicatie gewoon draaien.