The code below shows usage of two components: TsvWtsSession and TsvLaunchFrontEnd.
Let's start with basic actions: create a new service application and add service module to it. The TComService was used as a parent because we plan to implement COM-based interprocess communication between our service and its GUI.
The next step is to add all necessary components to it:
Now we need to set up LaunchFronEnd properties. It can be done with Object Inspector but it will be more flexible to set up it at runtime:
|
procedure TGuiService.ComServiceCreate(Sender: TObject);
var S: String;
begin
S:=ExtractFilePath(ParamStr(0));
SetLength(S,Length(S)-1);
S:=ExtractFilePath(S)+'011-GuiClient\GuiClient.exe';
svLaunchFrontEnd.ApplicationName:=S;
end; |
This code means that we expect to find the GuiClient.exe at path
../011-GuiClient/GuiClient.exe
Now we can launch the GUI at any time we need. For example we can do it immediately when service starts:
|
procedure TGuiService.ComServiceStart(Sender: TNtService;
var DoAction: Boolean);
var i: Integer;
Session: TsvWtsSessionInfo;
begin
svWtsSessions.UpdateSessions;
svWtsSessions.Lock;
try
for i:=0 to svWtsSessions.SessionCount-1 do
begin
Session:=svWtsSessions.Sessions[i];
if (Session.State <> WTSActive) then continue;
svLaunchFrontEnd.Launch(Session.SessionId);
end;
finally
svWtsSessions.Unlock;
end;
end; |
A few notes about this code:
- First of all we call UpdateSessions to refresh list of current sessions and their states.
- We use Lock to get access to list of sessions. When session list is not necessary we release it with Unlock call. Sure it is placed into "finally" section.
- We ignore inactive sessions
- If the session is active then we launch GUI with simple call. The only parameter indicates in what sessions the GUI should be launched. The TsvLaunchFrontEnd component does the job.
It may be useful to receive notification when the process is launched. It is easy to implement, just add OnProcessLaunch handler:
|
procedure TGuiService.svLaunchFrontEndProcessLaunch(Sender: TObject;
Process: TsvLaunchedProcess);
begin
EventLog.LogMessage('It is really launched!');
end; |
Another notification is fired when the process terminates:
|
procedure TGuiService.svLaunchFrontEndProcessTerminate(Sender: TObject;
Process: TsvLaunchedProcess);
var Session: TsvWtsSessionInfo;
begin
svWtsSessions.UpdateSessions;
svWtsSessions.Lock;
try
Session:=svWtsSessions.SessionsById[Process.SessionId];
if not Assigned(Session) or (Session.State <> WTSActive) then
begin
Process.Free; // We should cleanup LaunchedProcess
// entries if we do not need it more
exit;
end;
finally
svWtsSessions.Unlock;
end;
svLaunchFrontEnd.Launch(Process.SessionId);
end; |
Important thing is that the TsvLaunchFrontEnd component maintains the list of launched processes. The list can be accessed using LaunchedProcess[index], count of items can be get through LaunchedProcessCount. Items of the list are TsvLaunchedProcess. They are linked to the list, freeing of item removes it the from list too.
As you can see in the snippet above we check that GUI was closed by user and re-launch it again. Try to run the example and you will see that GUI always persists even it was terminated with task manger.
Another point where the process can be launched is the new user logon:
|
procedure TGuiService.svWtsSessionsSessionStateChanged(Sender: TObject;
Session: TsvWtsSessionInfo);
var i: Integer;
LP: TsvLaunchedProcess;
begin
EventLog.LogMessage(Format('Session state changed : SessionId = %d'+
' State = %d SID=%s',
[Session.SessionId, Integer(Session.State), svWtsSessions.LogonSid]));
// Check whether the user is active
// Exit if it is not
if Session.State <> WTSActive then exit;
// Look up for already launched process
for i := svLaunchFrontEnd.LaunchedProcessCount-1 downto 0 do
begin
LP:=svLaunchFrontEnd.LaunchedProcess[i];
if not LP.IsAlive then
begin
LP.Free; // We should cleanup LaunchedProcess
//entries for terminated processes
continue;
end;
if LP.SessionId = Session.SessionId then exit; // Yes, it still runs
end;
EventLog.LogMessage('Launching...');
svLaunchFrontEnd.Launch(Session.SessionId);
EventLog.LogMessage('Launched');
end; |
This code does a lot of checks:
- It ignores inactive sessions
- It walks through the list of launched processes. If GUI is alive then it exists
- In other case it launches the GUI
- It logs all events into system eventlog.
Finally add the timer handler to update sessions state regularly:
|
procedure TGuiService.svTimerTimer(Sender: TObject);
begin
svWtsSessions.UpdateSessions;
end; |
The code is finished and you can test it. Start the service, the GUI will appear. Try to close it and it will restart again. Try to logon another user and it will receive its own GUI. You can logon locally or using RDP, in any case you will see the GUI.
Return to examples index for more sample services.