Introduction
This article shows a technique to design Windows Service apps with the following goals:
- Can be debugged like a "Console" application without the need to invoke the "Installer" program.
- The main "Windows Service" code should be reusable and can be rapidly applied to any new "Windows Service" application development.
- Separates the "Server Logic" and allows dynamic switching if desired.
- The details of the "Windows Service" during installation are configurable via an XML file.
- The loading of the server logic is configurable via an XML file.
The sample code I presented here is the basic framework and has been tested with Visual Studio 2008 and .NET 3.5 Framework. But the concept should be applicable to other versions as well.
Background
If you have never created a Windows Service application, there is a "walkthrough" article on the MSDN library site. Obviously, there're many useful articles on The Code Project as well.
Using the Code
By using the presented technique, to create a Windows Service app will just require you to create a new "Console" project using the Visual Studio.
The sample Visual Studio solution consists of 4 projects. They are:
GDWS.Common - That's where the generic reusable code for the Windows Service sits.
GDWS.ExampleService - This is an example "server" logic that's aimed to be built as a .NET assembly.
GDWS.ExampleServiceProgram - This is an example "Console" app that actually invokes the server logic.
SetupExampleService - This is a typical "Setup & Deployment" project to demonstrate that this technique also works seamlessly with the standard setup mechanism.
In a nutshell, the Main() program is as simple as this:
namespace GDWS.ExampleServiceProgram
{
class Program
{
static void Main(string[] args)
{
ServiceMainProgram.ServiceMain(args);
}
}
[RunInstaller(true)]
public class ExampleServiceInstaller : CustomServiceInstaller
{
}
}
Class - CustomServiceInstaller
This class mainly reads the ServiceInstall.xml file in order to tell Windows Installer the "Service Name", the "Service Display Name", and the "Service Descriptions".
Class - ServiceMainProgram
In order to load the "server logic" dynamically, .NET Reflection is used to load the DLL that encapsulates the server logic. The two constructors are private, and you use the two static methods to invoke the process.
public class ServiceMainProgram
{
...
...
public static void Service(string[] args, Type type)...
public static void Service(string[] args)...
...
}
The static method that takes two arguments requires you to submit a Type, so that during creation of an object internally it can use Reflection to find out that the two required static methods StartThreadProc and StopThreadProc are present.
The second static method that takes only one argument reads the name of the Type from the ServiceConfig.xml file, which also tells it where to find the server logic assembly DLL. Once the path of the DLL is located, the assembly is loaded by using Reflection.
private Type LoadAssemby(string configFileName)
{
...
...
Assembly asm = Assembly.LoadFile(Path.GetFullPath(assemblyFullPath));
...
Type type = asm.GetType(typeName);
...
}
Once the Type is found and checked, the following code can utilize the reflected methods:
private void Run()
{
if (debugMode) RunDebug();
else
{
ServiceBase[] servicesToRun = new ServiceBase[]
{ new GenericService(threadProcType) };
ServiceBase.Run(servicesToRun);
}
}
private void RunDebug()
{
...
...
MethodInfo mStart = threadProcType.GetMethod(START_THREAD_PROC);
mStart.Invoke(null, null);
bool stop = false;
while (!stop)
{
...
if (k.KeyChar == 'q' || k.KeyChar == 'Q')
{
...
MethodInfo mStop = threadProcType.GetMethod(STOP_THREAD_PROC);
mStop.Invoke(null, null);
stop = true;
}
...
}
}
Debugging
For debugging purposes, define a command line argument debug or under the Command Prompt, just type yourservice.exe debug to invoke the program into debug mode.
Installing as a Proper Windows Service
You can install the sample service app using the bundled Setup project.
To quickly test the app as a Windows Service, you can use the installutil.exe and uninstall it by calling installutil.exe /u.
ServiceConfig.xml
="1.0" ="utf-8"
<ConfigService>
<ServiceName value="ExampleService"/>
<ServiceDisplayName value="An Example Service"/>
<ServiceDescription value="An example service
that demonstrates a generic and dynamic technique."/>
</ConfigService>
ServiceInstall.xml
="1.0" ="utf-8"
<InstallService>
<ServiceAssemblyFullPath value=
"..\..\..\GDWS.ExampleService\bin\Debug\GDWS.ExampleService.dll"/>
<TypeName value="GDWS.ExampleService.ThreadProcExample"/>
</InstallService>
Please note that, although the ServiceAssemblyFullPath is specified as a relative path, the code will try to locate it locally if it cannot find it at the first instance.
History
- 2008-01-22: Article created
| You must Sign In to use this message board. |
|
|
 |
|
 |
When i try to run the console exe in release mode, system displays the following message "Can't start service from command line or a debugger..."
I have verified that the service is installed and running on the system.
Is there ant thing i'm, missing here... Kindly help me out here..plz..
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
How did you install it? Try using "installutil.exe" to install it, and make sure the ServiceInstall.xml is filled in. I think your problem is similar to someone else that have asked me here before.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Even i tried to run the GDWS.ExampleServiceProgram.exe after intalling service via installuil, I still gets prompts that service is either not installed or is not started.
However on viewing the service in services msc, I didn't find the service which I registered using intallutil.
Please help me out here, as I really wanna use your proposed approach in my sample app.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
The concept for installing the service is to use "installutil", for example:
cd c:\example c:\windows\microsoft.net\framework\v2.0.50727\InstallUtil.exe /i GDWS.Example.exe
Please make sure the GDWS.Example.exe, ServiceConfig.xml and ServiceInstall.xml files are located in the same folder.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Change public static void ServiceMain(string[] args) to something more meaningful like public static void Start(string[] args)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
"start", "stop", ... have their own specific meanings in the context of Windows Service. The process of developing a Windows Service in .NET is similar to the old days Win32, hence calling it "ServiceMain" for an obvious reason as it's where it runs after "main" (program entry point).
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Hi,
I made TWO different Windows services based on the GDWS article in two different solutions. I made two different msi-files with differently named folders, DLLs, etc. They run well individually on my PC. But after installing one service, installation of the second service cause the Windows message 'Another version of this project is already installed. etc. When installing from Visual Studio there is also some reference between my two solution folders. There must be some setting to make these two services different for the Windows installer. Who can help me?
Thanks, Kees
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
If you zip up your 2 solutions & stuff and upload it somewhere for me to download, I can take a look at your problem. You should first try using "installutil.exe" to install the 2 services. If they're all fine, then it's the install program's problem and it's difficult to tell without any info on how u made them.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hello,
From your test project the service works. But when I install it via installutil, when I start, I receive this error : "Error 1053: The service did not respond to start or Control request in a timely fashion"
I checked on Google, there is a fix for .NET 1.1 but it's a .NET 3.5 project. do you have an id what to do ? The target machine is Windows XP. Thanks,
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Locate the exe file which can be found in "bin\debug" folder. There should also be 2 xml files as well. One is used during installation, and the other is used for configuring what DLL assembly to load. Make sure all the paths are correct. To install it, type:
installutil GDWS.ExampleServiceProgram.exe
To uninstall it, type:
installutil -u GDWS.ExampleServiceProgram.exe
Hope this help. Otherwise let me know more details on how you actually try to install it as a service.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
It's my fault, I forgot to copy 2 files.
In the "ThreadProc" I do some WebRequest, that's work. In some condition, I'd like to launch a .bat file or an other application. I tried with "notepad", display a message in the console Windows but nothing happen. the code work because, I test it in a small console application. Is there a limitation to launch process in a Windows service ?
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
It's not the limitation of the code, it's the security model that .net imposes on "services". For example, to launch a "notepad" within the service, the "service" need to have enough privileges to do so, plus you need to "allow service to interact with desktop" need to be checked under "services"'s "Log On" tab. This also implies that a user has logon, otherwise the notepad won't be able to launch "notepad" because the shell's not running. I suggest you read more on MSDN regarding to how a "service" work then you'll see how you can properly implement this sort of things.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
I didn't go through the full code but just thought to ask this question..
What is your reasoning behind making the method named "LoadAssemby" a protected and also virtual
ZeroResult.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
protected helps in limiting access of class member and virtual allows to give new definition of inherited member.
|
| Sign In·View Thread·PermaLink | 3.67/5 |
|
|
|
 |
|
 |
My mistake, wrote the code yesterday in a rush. In the context of the sample code, this method and other methods should just be "private". The class has only private constructors. Thanks for pointing it out.
|
| Sign In·View Thread·PermaLink | 2.67/5 |
|
|
|
 |
|
|
 |
|
|