Using VS - DevSource
DevSource: Microsoft Developer Resource DevSource Home Sponsored by Microsoft Home Add Ons Architecture Languages Techniques Using VS Forums
Home arrow Using VS arrow Writing a Managed Windows Service with Visual C++
Writing a Managed Windows Service with Visual C++
By John Mueller

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 tasks—everything 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.

  1. 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.

  2. NOTE: Because a Windows service doesn't have a user interface, you can't use controls with it.

  3. 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.

  4. Notice the Add Installer link in the Properties dialog box; click it only after you configure the Windows service.

  5. Perform any required configuration. The only property that you need to change for this example is ServiceName. The example uses TrackAccess.

  6. TIP: Always configure the Windows service before you add the installer. It's a lot of work to perform this task later.

  7. Click the Add Installer link at the bottom of the Properties dialog box. Visual Studio displays another Designer window—this 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.

  8. 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.

  9. 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 directly—it 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 code—what 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 start—at 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 patience—hopefully, Microsoft will provide a fix for an otherwise easy process.




Discuss Writing a Managed Windows Service with Visual C++
 
>>> Be the FIRST to comment on this article!
 

 
 
>>> More Using VS Articles          >>> More By John Mueller
 



Microsoft's Future: A Chat With Their CTO, Barry Briggs

Play Video >

All Videos >

Julia explores the Robotics Studio!

Read now >

Messages to Bill Gates!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.