views:

1222

answers:

5

I have a service application built in Delphi that works great. It does exactly what I want it to do and all is happy. All is fine until I want to run two (or more) instances of that service on a single machine. Since the service name is hard coded into the program (via the Name property of the service), I can only install the service once on any given computer. If I try to modify the Name property at run-time, the service does not respond unless the Name property is set to the same thing that was set during design time.

I have done a workaround for this where I have all of the code that is not interacting directly with the service control manager encapsulated out into separate unit(s). Then I write a separate Delphi project for each instance that I want of the service that has just enough code to launch itself and start running the main code.

This method is, in my opinion, ugly and is certainly inefficient. It works okay for two instances, but then we need a third and a fourth and ...

Is there any way that I can modify my code so that I have just one Delphi project that can install and run itself as multiple service instances with some simple run-time input (e.g. command line flag)?

Or perhaps a broader question: Is there a "right way" to accomplish goal?

+3  A: 

You can create your service with multiple threads internally, each one acting like it's own version/copy of the service. You control it with the Service Controller API, IIRC.

Ken White
I thought about this, but it seemed like it was basically creating duplicate code. The services features of windows already has the thread controlling code built-in. Why not leverage that rather than rolling my own?
Scott W
No, that's not what I'm saying. Windows has the service controller code for you to use to start/stop multiple threads. Move your service code into a TThread, and for each instance of your service just start another thread. Better explanation, I hope, of what I intended.
Ken White
I'm sorry, I still don't think that I understand. How does this appear in the Services control panel (one entry/multiple)? Are you suggesting that I use the Service Control API within my code (e.g. call ControlService) or just handle the events (e.g. ServiceStart, etc.)?
Scott W
It would appear as one entry, because of only one display name. You'd respond to ServiceStart to launch the first thread, and use the Service Control API to have that first thread start/stop individual threads after it was running.
Ken White
+2  A: 

Well yes it is possible to install multiple instances of the same service, you simply need to dynamically alter the name at install time (not runtime) however this does not make it desireable. (there is some sample code on Code project http://www.codeproject.com/KB/dotnet/MultipleInstNetWinService.aspx)

I would however be inclined to rethink your approach, service processes themselves are really meant to be singleton, if you need multiple instances of a process being run, maybe your service should just control and manage the multiple processes rather than being the process.

Tim Jarvis
Thanks, but I'm not sure that this applies to Delphi. This is what I tried to do -- change the displayName during the install event -- but then the service choked when it tried to start under the "wrong" name.
Scott W
Also, please see my comment to Ken's answer... isn't doing the multi-threaded controller kind of service unnecessarily rolling my own when Windows could already do the controller part for me?
Scott W
Follow Ken's advice. What you are attempting to do is an example of bad design. Could you get it to work? Sure. Should you? No, not if you've got the resources to do it correctly (and this isn't just some one-off).
Mick
@Mick: are you saying that Ken's advice is bad design, or what I want to do in general is bad design? Why?
Scott W
+3  A: 

You haven't made it clear what you have tried to change in the TService subclass.

Have you added a "BeforeInstall" handler?

Something like:

procedure TServiceMain.ServiceLoadInfo(Sender : TObject);// new method, not an override
begin
  Name := ParamStr(2);
  DisplayName := ParamStr(3);
end;

procedure TServiceMain.ServiceBeforeInstall(Sender: TService);
begin
  ServiceLoadInfo(Self);
end;
procedure TServiceMain.ServiceCreate(Sender: TObject);
begin
  ServiceLoadInfo(Self);
end;

If you do this regularly, subclass TService to do thie in the Constructor instead.

You should do the same in the BeforeUninstall as well - point both events at the same method.

C:\>servicename /install MyService "My Service Description"
Gerry
If I do this, then the service does not run unless ParamStr(2) is equal to the value that has been set for Name in the Object Inspector in the IDE. If the ParamStr(2) is different, then when the service is started, it goes into a perpetual "Starting" state and never executes.
Scott W
Sorry, this is only part of what is needed. You need the the OnStart event to call this as well. (Working from memory here!)
Gerry
I cannot find this ServiceLoadInfo method that you are using. Is that a standard method, or just a suggestion to write a method that figures out what name it is being called in this instance?
Scott W
OK, I got it. I had to modify the ImagePath for the service in the registry to include the name of the service that I wanted as a parameter on the command-line. Thanks!
Scott W
I have updated the example. I made a mistake when extracting code from and service app.
Gerry
A: 

Wrap all of your code into a class that inherits from TThread.

When your service starts it will read a number from a settings file or from the registry and create that many instances of your class.

Each instance runs independently.

To change the number of running instances you could shut down the service, edit the setting (in a file or registry) and restart the service.

Chapel
With such a setup, let's say that I start the service with three threads. Could I then stop thread 2, leaving threads 3 and 1 running (without writing a bunch of code to effectively re-create the service controller)?
Scott W
With the suggestion I made, yes. Your main thread would handle requests to start and stop child threads; those child threads would be the ones doing the actual work. You'd communicate with the service's main thread with the Service Control API. (As I mentioned in my comment to you below my answer.)
Ken White
A: 

The accepted answer above was awfully helpful.

Code I used:

procedure TService1.ServiceAfterInstall(Sender: TService);
begin
//http://stackoverflow.com/questions/612587/is-it-possible-to-install-multiple-instances-of-the-same-delphi-service-applicati
//http://www.c-sharpcorner.com/UploadFile/timosten/DynamicServiceInCSharp11262005062503AM/DynamicServiceInCSharp.aspx?ArticleID=4d5020e4-7317-425c-ab29-5bf37a1db421
//http://support.microsoft.com/kb/137890
  SaveRegSetting('\SYSTEM\CurrentControlSet\Services\' + Name, 'ImagePath', ParamStr(0) + ' --name=' + Name, HKEY_LOCAL_MACHINE)
end;

procedure TService1.ServiceCreate(Sender: TObject);
begin
  Name := Trim(FCommandLineOptions.Values['name']);
  DisplayName := Name;
end;

SaveRegSetting is my own procedure and FCommandLineOptions is an object that tokenises the command line parameters.

cja