HomeUsing VS Writing a Managed Windows Service with Visual C++
Writing a Managed Windows Service with Visual C++ ByJohn Mueller 2004-11-04
Article Rating: / 6
Rate This Article:
Add This Article To:
Writing a Windows service used to be hard work. John Mueller shows how using Visual Studio .NET makes the process a lot easier.
A Windows service is an application that sits in the background, waiting for you to call it or for some system event to happen. The Services console, found in the Administrative Tools folder of the Control Panel, is packed with examples of these applications. A quick look will show you that they perform a wide range of taskseverything from keeping track of system
events to providing plug-and-play support. You probably have a Windows service monitoring your uninterruptible power supply (UPS), and another just waiting to create a dial-up connection should you need it.
In short, Windows services are very useful applications. Unfortunately, until now, writing a Windows service using any language was difficult (perhaps impossible) for many people. Visual Studio .NET takes a lot of the pain out of writing a Windows service by providing an object model for the code and wizards to create the project. This article gets you going with a basic Windows service
and an application that calls it.
NOTE: Although you'll find services in every version of Windows, I tested the example in this article to run on NT-based systems, including Windows 2000 and Windows XP. You can use any version of Visual C++ .NET to write the code in this example.
Defining the Windows Service Project
ADVERTISEMENT
The first task you need to accomplish is creating a Windows service project.
This project relies on a wizard that creates the basic infrastructure for you. The following steps describe how to create the Windows service. I'm assuming that you've already started Visual Studio .NET and opened the New Project dialog box.
Select the Windows Service (.NET) project in the New Project dialog box.
Then type a name for the Windows service (the example uses SimpleService) and
click OK. In the resulting Designer window, you can drag components from Server
Explorer to the Windows service. For example, if you want to monitor the current
processor usage with your new Windows service, you could drag a
performance-monitoring component from Server Explorer to perform the task. For this example, we won't use any special components.
NOTE: Because a Windows service doesn't have a user interface, you can't
use controls with it.
Select the Designer window to change the IDE focus. The Properties dialog box now contains all of the properties for the Windows service, as shown in Figure 1. Notice the Add Installer link at the bottom of the Properties dialog box. You'll use this link to create an installer after you configure the Windows service.
Notice the Add Installer link in the Properties dialog box; click it only after you configure the Windows service.
Perform any required configuration. The only property that you need to
change for this example is ServiceName. The example uses TrackAccess.
TIP: Always configure the Windows service before you add the installer. It's a lot of work to perform this task later.
Click the Add Installer link at the bottom of the Properties dialog box.
Visual Studio displays another Designer windowthis one for the installer.
The installer requires the use of two components that Visual Studio adds for you
automatically: serviceProcessInstaller1 and serviceInstaller1. These components help you to manage the Windows service installation.
Select the serviceProcessInstaller1 control and choose an account
property that matches the Windows service requirements. The account determines
the security level of the Windows service; you can change it later, but
it's best to set it now. Many Windows services use the LocalSystem account,
but the User account is more secure because the Windows service can't be
tricked into performing tasks the user isn't allowed to perform. For this
example to work properly, though, you must select the LocalSystem
account.
Select the serviceInstaller1 control. As a minimum, set the DisplayName property to the name you want to see in the Services console and the StartType property to the method you want to use for starting the Windows service.
Generally, you should use a DisplayName value that's easy to read and
understand; the example uses Simple Windows Service. Depending on what task your
Windows service performs, you'll likely need to specify manual or automatic
starting.
Adding Some Simple Test Code
At this point, your Windows service is configured, but it won't do anything. The code will even compile and you could install it. The Windows service comes with several predefined methods, but the two most important are OnStart() and OnStop() because they define how the Windows
service reacts to standard commands from the Services console. You'll also need to define some custom methods to perform the work you intend for the
Windows service to do. In this case, the Windows service will simply keep track
of the number of times it has been accessed. Listing 1 shows some code to
perform this task.
Listing 1 Defining What the Windows Service Will Do
private:
// A variable that tells how many times the Windows
// service is accessed.
Int32 _NumberOfAccesses;
protected:
void OnStart(String* args[])
{
// Set the number of accesses to 0.
_NumberOfAccesses = 0;
}
void OnCustomCommand(Int32 Command)
{
// Execute the default command. Microsoft reserves command
// numbers 0 through 127 for default service behaviors.
if (Command < 128)
ServiceBase::OnCustomCommand(Command);
// Increment the number of accesses. The client application
// calls this command number to increment the access count.
if (Command == 128)
DoAccess();
// Make an event log entry. Because the Windows service can't
// provide a user interface, it uses the event log for communication.
if (Command == 129)
GetAccess();
}
public:
void DoAccess()
{
// Increment the number of accesses.
_NumberOfAccesses++;
}
void GetAccess()
{
StringBuilder *Output; // The output data.
// Create the output data.
Output = new StringBuilder();
Output->Append("The number of user accesses is: ");
Output->Append(_NumberOfAccesses);
// Write an entry in the event log.
this->get_EventLog()->WriteEntry(
"TrackAccess",
Output->ToString(),
EventLogEntryType::Information,
1200,
99);
}
NOTE: I use a StringBuilder because it's automatically garbage-collected and ensures that there are no memory leaks in the Windows service. In addition, this is a managed application, so the StringBuilder is the most appropriate method of building the string.
The _NumberOfAccesses variable simply holds the number of times a test application has accessed the service since you started the service. Whenever the service is restarted, Windows calls OnStart(), which resets _NumberOfAccesses to 0. In this case, there isn't any need for an OnStop() process because OnStart() takes care of the required processing.
The OnCustomCommand() method is the cornerstone of the Windows service. An application can't access the Windows service directlyit must go through Windows to perform the task, using a task number. Windows reserves tasks 0 through 127 for its own use, so the first open task number is 128. The code automatically passes all of the default commands to the base class. When the code sees a 128 command, it updates the access count by calling DoAccess(). Likewise, when the code sees a value of 129, it calls GetAccess to create an event log entry that contains the number of accesses.
The two public methods, DoAccess() and GetAccess(), are relatively simple. Calling DoAccess() merely increments _NumberOfAccesses. Calling GetAccess() creates a StringBuilder
containing a message that the code places in the event log by using the EventLog::WriteEntry() method.
Registering the Service
You can't just start the service. Even though this is an executable, you have to install the service in a particular way. The standard method is to open the command prompt and use the InstallUtil command to perform the task.
This method should work with the example because it works with Windows services created using other languages. Unfortunately, it doesn't work very well, so you need to rely on a second method: typing the service name at the command prompt with the -Install switch. You also need to provide the full path to the executable (even though you're already in the executable path), which means typing something like this to install the Windows
service:
C:\MyService\SimpleService.exe -Install
Yes, the installation code is that flawed.
Unfortunately, Microsoft doesn't provide code to uninstall the Windows
service. Consequently, I added the code shown in Listing 2 to the
SimpleServiceWinService.CPP file. This code appears immediately after
the install code in the _tmain method.
Listing 2 Adding Uninstall Functionality to the Windows Service
if (_tcsicmp(argv[1], _T("-Uninstall")) == 0)
{
//Install this Windows Service using InstallUtil.exe
String* myargs[] = System::Environment::GetCommandLineArgs();
String* args[] = new String*[myargs->Length];
args[0] = S"-u";
args[1] = (myargs[0]);
AppDomain* dom = AppDomain::CreateDomain(S"execDom");
Type* type = __typeof(System::Object);
String* path = type->get_Assembly()->get_Location();
StringBuilder* sb =
new StringBuilder(path->Substring(0, path->LastIndexOf(S"\\")));
sb->Append(S"\\InstallUtil.exe");
dom->ExecuteAssembly(sb->ToString(), 0, args);
}
The code is a simple modification of the installation code. When the Common
Language Runtime (CLR) starts the application and you provide
-Uninstall as the command-line parameter, argv[] actually
contains two arguments: the -Uninstall switch and the application name
as you type it at the command line. When you don't include the path
information, neither does CLR, so the InstallUtil utility doesn't
know where to find the executable; it reports an error such as this:
System.IO.FileLoadException: Unverifiable image 'SimpleService.exe' can not be run.
To uninstall a Windows service, the code must call InstallUtil using
the ExecuteAssembly() method. This method accepts the name of the
application (including full path) and an array containing arguments,
args. The first argument in args is -u, which tells
InstallUtil to remove the Windows service. The second argument is the
full path to the Windows service executable, including the EXE
extension. Notice how the code obtains this information using the
GetCommandLineArgs() method and places it in the args array.
Notice that there isn't a single line of error-checking or augmentation
codewhat you type at the command line is what the code will use.
Now that the code has created a list of arguments, it has to create the call
to InstallUtil. It begins by getting the path to the simple object that
CLR possesses, System::Object. This path actually leads to
mscorlib.dll. Therefore, the code strips off the executable information
from the path it just obtained and appends InstallUtil.exe to it. The
final step is to uninstall the Windows service. It's not pretty, but it
works, and you don't have to figure out precisely what path information to
provide.
Creating the Test Program
You now have a Windows service installed. It's easy to verify the
presence of the service by using the Services console, found in the
Administrative Tools folder of the Control Panel. In fact, open the Services
console now to check for the service. Verify that the service is running, or the
test application will fail. If the service isn't running, right-click it
and choose Start from the context menu.
The test application is a generic Windows form application. You'll need
to add buttons for starting and stopping the service, register a service access,
and display the number of service accesses as a minimum. After you add the
buttons, open Server Explorer. Locate the service you just created and drag it
to the form. The IDE will add a new ServiceController component that you can use
to access the service. The example names this component svcTrackAccess. Listing
3 shows the code required to test the Windows service.
Listing 3 Testing the New Windows Service
private: System::Void Form1_Activated(System::Object * sender,
System::EventArgs * e)
{
// Validate the current service status. Enable or disable
// buttons as necessary.
if (svcTrackAccess->Status == ServiceControllerStatus::Running)
btnStart->Enabled = false;
else
{
btnStop->Enabled = false;
btnAccess->Enabled = false;
btnGetAccess->Enabled = false;
}
}
private: System::Void btnStart_Click(System::Object * sender,
System::EventArgs * e)
{
// Start the service.
svcTrackAccess->Start();
// Wait for the service to start.
svcTrackAccess->WaitForStatus(
ServiceControllerStatus::Running,
System::TimeSpan::FromMilliseconds(2000));
// Change the button configuration to match
// the service status.
if (svcTrackAccess->Status == ServiceControllerStatus::Running)
{
btnStart->Enabled = false;
btnStop->Enabled = true;
btnAccess->Enabled = true;
btnGetAccess->Enabled = true;
}
}
private: System::Void btnStop_Click(System::Object * sender,
System::EventArgs * e)
{
// Verify that we can stop the service.
if (svcTrackAccess->CanStop)
// Start the service.
svcTrackAccess->Stop();
else
{
// We can't stop the service.
MessageBox::Show("Service Doesn't Support Stopping");
return;
}
// Wait for the service to start.
svcTrackAccess->WaitForStatus(
ServiceControllerStatus::Stopped,
System::TimeSpan::FromMilliseconds(2000));
// Change the button configuration to match
// the service status.
if (svcTrackAccess->Status == ServiceControllerStatus::Stopped)
{
btnStart->Enabled = true;
btnStop->Enabled = false;
btnAccess->Enabled = false;
btnGetAccess->Enabled = false;
}
}
private: System::Void btnAccess_Click(System::Object * sender,
System::EventArgs * e)
{
// Access the service.
svcTrackAccess->ExecuteCommand(128);
}
private: System::Void btnGetAccess_Click(System::Object * sender,
System::EventArgs * e)
{
EventLog *MyEvents; // Event log with service entries.
// Generate an event log entry with the number of accesses.
svcTrackAccess->ExecuteCommand(129);
// Open the event log.
MyEvents = new EventLog("Application");
// Look for the correct message.
for (int Count = 0; Count < MyEvents->Entries->Count; Count++)
{
// Get the event registered by the service.
if (MyEvents->Entries->get_Item(Count)->CategoryNumber == 99)
if (MyEvents->Entries->get_Item(Count)->EventID == 1200)
// Display the message
MessageBox::Show(MyEvents->Entries->get_Item(Count)->Message);
}
}
What appears to be a lot of code really isn't. The code begins with the
Form1_Activated() method that checks the current status of the service.
When the service is running, the code disables the Start button. Likewise, a
stopped service disables the Stop, Access, and Get Access buttons. I included
this code so you could see how to configure a support application during
startup.
When a user clicks Start, the btnStart_Click() method begins by
starting the service. However, it's not a good idea to simply move on to
the next step because a service can require several seconds to start.
Consequently, the code uses the WaitForStatus() method to wait for the
service to startat least it waits for two seconds. If the service takes
longer than two seconds to start, in this case, there's probably something
wrong and you shouldn't wait any longer. Your own Windows services will
require timing because not all services are the same. When the service starts as
expected, the code again enables and disables buttons as needed.
Clicking Stop calls the btnStop_Click() method. Notice that, in this
case, the code checks to ensure that it can even stop the service. Some services
are marked so that Windows can't stop them. When this situation occurs, the
code displays a message box telling the user that it can't stop the
service, and then exits. Otherwise, the code calls the Stop() method
and waits as with the Start() method for the service to signal that it
has stopped. Again, the code changes the buttons to match the situation.
The user clicks Access to increase the access count. All the code has to do, in this case, is call ExecuteCommand() with the correct command number, 128.
Remember that the Windows service can't interact directly with your application, but it can communicate with the event log. Consequently, when the user clicks Get Access, the code begins by calling ExecuteCommand()
with the 129 command code. This act generates an event log entry. Now the code opens the application event log and begins looking through the entries for the one made by the Windows service. When it finds this entry, the code displays a message that contains the number of user accesses.
Bottom Line
Windows services can perform any of a number of background tasks. In the past, Microsoft made it incredibly difficult to create a Windows service, but Visual Studio .NET changes all that. Now you can create Windows services with relative ease. The only caveat is that installing the service could try your patiencehopefully, Microsoft will provide a fix for an otherwise easy process.