C++ builder윈도우 서비스

2009. 9. 25. 11:01언어/C++ Builder

볼랜드 포럼에서 퍼왔습니다.

 

In Part I, we began this series with a description of services and how they work under NT. In this installment, we'll begin by discussing how to write a simple service. We'll also try to understand the C++Builder-generated code for services, and talk about service threads and installing and testing services.

 

Writing a Simple Service
The example program for this section is called BeepSrvc. This service does nothing more than beep the PC speaker once each second. A service that beeps every second (or some other interval) is sort of the "Hello World" of NT services. It's not exactly exciting, but it will show you how services work. The service's name is BeepService. The header and source for the BeepService unit is shown in Figures 1 and 2.

 

#ifndef BeepSrvcUH
#define BeepSrvcUH
 
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <SvcMgr.hpp>
 
class TBeepService : public TService
{
__published:     // IDE-managed Components.
   void __fastcall Service1Execute(TService *Sender);
private:         // User declarations.
public:          // User declarations.
   __fastcall TBeepService(TComponent* Owner);
  PServiceController __fastcall GetServiceController(void);
   friend void __stdcall ServiceController(unsigned CtrlCode);
};
 
extern PACKAGE TBeepService *BeepService;
 
#endif
Figure 1: The header for the BeepService unit.
 
#include "BeepSrvcU.h"
 
#pragma package(smart_init)
#pragma resource "*.dfm"
 
TBeepService *BeepService;
 
__fastcall TBeepService::TBeepService(TComponent* Owner)
        : TService(Owner)
{
}
 
PServiceController __fastcall TBeepService::GetServiceController(void)
{
   return (PServiceController) ServiceController;
}
 
void __stdcall ServiceController(unsigned CtrlCode)
{
  BeepService->Controller(CtrlCode);
}
 
void __fastcall TBeepService::Service1Execute(TService *Sender)
{
   while (!Terminated)
  {
    MessageBeep(0);
    Sleep(1000);
    ServiceThread->ProcessRequests(false);
  }
}
Figure 2: The source code for the BeepService unit.
 

As you can see from these listings, this simple service contains very little code. Most of the hard stuff is handled behind the scenes by the TService and TServiceApplication classes. In fact, I wrote only four lines of code for this service. The rest of the code was generated by C++Builder. I'll address the code generated by C++Builder in the following section.

 

This service uses default values for all of the service's properties except for the Name and DisplayName properties, both of which I have set to BeepService.

 

This service uses the default values for all other properties, allowing the service to be stopped, paused, and continued from the SCP or by using one of the service command-line utilities. The defaults also specify that this is a Win32 service, that it is not interactive (it cannot display a dialog box to the user), and that it is set to auto-start when NT boots.

 

Tip
You will save yourself some headaches if you set the Name and DisplayName properties to the same value. In fact, you'll notice that when you set the Name property in the Object Inspector, the DisplayName property changes to match. It's easier to manage your service during development when both the DisplayName and Name have the same value.

 

When I first wrote this service, I set the DisplayName property to BeepService and the Name property to BeepSrvc. I then used the SC.EXE utility to start and stop the service. I typed the following from the command line:

 

sc stop BeepService
 

I kept getting an error from SC.EXE when using this syntax. It took me a couple of minutes to realize that I was passing the service's display name to SC.EXE when I should have been passing the service's actual name. The display name is simply the text that is displayed in the SCP for the service. The service name is the text that the SCM uses to refer to the service. The proper command-line syntax for stopping the service should have been the following:

 

sc stop BeepSrvc
 

By setting the Name and DisplayName properties to the same value, I eliminated the confusion caused by using two different values for these properties.

 

Understanding the C++Builder-generated Code for Services
Before we get into what little code this service contains, I want to spend a moment discussing the code that C++Builder generates. You will find this code near the top of the main source unit:

 

PServiceController __fastcall TBeepService::GetServiceController(void)
{
   return (PServiceController) ServiceController;
}
 
void __stdcall ServiceController(unsigned CtrlCode)
{
  BeepService->Controller(CtrlCode);
}
 

Each service must have a function that processes service control requests. This function is commonly called the service handler. The service handler is registered with Windows by the TService class. As with many Windows callbacks, the service handler must be a standalone function. (It can't be a class member function.) The code you see here is the mechanism the VCL uses to implement the service handler. The implementation in SvcMgr.pas looks like this:

 

FStatusHandle :=
  RegisterServiceCtrlHandler(PChar(Name), GetServiceController);
 

Granted, this is Object Pascal code, but it's easy enough to understand even if you aren't a Pascal programmer. This code calls the Windows RegisterServiceCtrlHandler function, passing the service name in the first parameter. The second parameter is a call to the service's GetServiceController method. As you can see from the earlier code snippet, this method returns a pointer to the standalone ServiceController function. Once registered, the ServiceController function will be called each time the service receives a control request from the SCM. It's not vital that you understand service control requests at this time, but I wanted to explain the meaning of these two functions.

 

You may have noticed that the ServiceController function calls the service's Controller method. The Controller method is declared as protected in the TService class. How does the standalone ServiceController function gain access to the Controller method? If you look at the service's class declaration in Figure 1, you will notice that the ServiceController function is declared as a friend of the service class:

 

friend void __stdcall ServiceController(unsigned CtrlCode);
 

This makes it possible for the ServiceController function to call the protected Controller method.

 

Note: There is a minor bug in the code generation for service applications. When you create a new service, C++Builder gives the service a default name of Service1. It then generates a ServiceController function that looks like this:

 

void __stdcall ServiceController(unsigned CtrlCode)
{
  Service1->Controller(CtrlCode);
}
 

Now let's say you change the service's Name property to MyService. C++Builder will change all of the references to Service1 with MyService, except the reference in the ServiceController function. Before your code will compile, you will have to manually change the code in the ServiceController function so that it uses the correct class name. In this case, you would have to modify the code as follows:

 

void __stdcall ServiceController(unsigned CtrlCode)
{
  MyService->Controller(CtrlCode);
}
 

Service Threads and the OnExecute Event
Each service has its own thread. The ServiceThread property is a pointer to the service's internal thread. ServiceThread is an instance of the TServiceThread class. TServiceThread is derived from TThread. TServiceThread only provides one method beyond the usual methods of TThread. That method is ProcessRequests and its purpose is to process service requests sent to the service by the SCM.

 

For simple services, you can use the service's built-in thread. That's what the BeepService example does. All of the code for BeepService is in the service's OnExecute event handler:

 

void __fastcall TBeepService::Service1Execute(TService *Sender)
{
   while (!Terminated)
  {
    MessageBeep(0);
    Sleep(1000);
    ServiceThread->ProcessRequests(false);
  }
}
 

This code is fairly straightforward. It simply executes a loop as long as the service thread is still running. The code within the loop beeps the PC speaker, sleeps for one second, and then calls the service thread's ProcessRequests method. The call to ProcessRequests is vital. If you don't call ProcessRequests, your service will execute the loop once and then stop.

 

This service is not very exciting. In fact, it's downright annoying when it's running. Still, it's a perfectly valid service and shows the simplest type of service you can write. What may not be obvious, though, is all the work being done behind the scenes by the VCL. The code for this service is available for download; see end of article for details. Load the project in C++Builder and build it. You can then proceed to the next step, installing the service.

 

Installing the Service
A service must be installed after it is built. Installing a service registers the service with the SCM. The SCM, in turn, adds the service's configuration information to the registry. Figure 3 shows the registry key for BeepService.

 


Figure 3: The registry key created by the SCM when it registered BeepService.
 



To install a service, run the service application from the command line using the INSTALL switch:

 

beepsrvc /install
 

After a second or so, you will see a message box that reads as follows:

 

Service installed successfully
 

If the service didn't install successfully, you'll need to check your code to see if you've missed anything. If you have loaded BeepService from the accompanying download, it will install without error.

 

Note: NT services are, naturally, for use with the NT operating system. If you attempt to run a stock service application from Win9x, it will return without doing anything at all. Technically speaking, it is possible to run services in Win9x using various tricks and utilities. On the other hand, the Win9x operating systems certainly do not have full support for services and I do not attempt to cover the use of services on those platforms in this article series.

 

To uninstall a service, first stop the service if it is started. (I discuss controlling services in the next section.) Run the service application again, this time with the UNINSTALL switch:

 

beepsrvc /uninstall
 

A message box will tell you that the service was successfully uninstalled. Understand that uninstalling a running service does not terminate the service; it only removes the service from the registry. For that reason, you should stop the service before uninstalling the service.

 

Note: While developing a service, you will continually be in a cycle of changing code, compiling, and running the service to test the results of your code changes. It is not necessary to uninstall the service each time you build the service application from C++Builder. It is, however, necessary for you to stop the service before building the service application. Failure to stop the service prior to building the service application will result in a C++Builder linker error message that reads as follows:

 

Could not open D:\Services\BeepSrvc.exe (program still running?).
 

You will know that you need to stop the service before continuing if you get this error message when building the service application.

 

I usually keep a command prompt box open when developing a service. Once I have typed the install or uninstall command, I can easily install or uninstall a service by taking advantage of NT's capability to recall commands typed at the command prompt (using the up and down arrows on the keyboard).

 

Testing the Service
If the service installed successfully, open the SCP from the Control Panel and you will see the service listed among the other installed services. You will see the service name, BeepService, and the Startup type listed as Automatic. The Status column will be blank, indicating that the service is stopped. Even though the service is configured to start automatically, this doesn't happen until the system is restarted. You will have to reboot NT to test your service's auto-start capability.

 

To start BeepService, click the Start button on the SCP. NT will start the service and you will hear the PC speaker beep every second. Figure 4 shows the SCP when starting BeepService.

 


Figure 4: The SCP will start BeepService when you click the Start button.
 



After the service has started, the Status column in the SCP will show Started. Test the service's capability to pause by clicking the Pause button. Restart the service by clicking the Continue button. Stop the service and restart it. You will notice that the service responds to the service control requests automatically. It's impossible to overstate the benefits of writing services with C++Builder. If you've ever had to write a service using the API, you can appreciate all the work that the VCL is doing in the background.

 

Most of the time, it's convenient to simply use the SCP to start, stop, pause, continue, or configure your service. One annoyance of the SCP is that it has no associated taskbar button. This means that you can't easily find the SCP when it gets lost behind other windows. Further, the SCP has no capability to refresh its display. If the SCP is already running and you double-click its icon in the Control Panel, NT simply brings the SCP to the top - it won't update its display. Say, for example, that you have the SCP open and you install your service from a command prompt box. If you switch back to the SCP, your service won't show up in the list of installed services. You have to close the SCP and then reopen it by double-clicking the services icon in the Control Panel. If for some reason your service doesn't show up when installed, or doesn't disappear when uninstalled, remember to close the SCP and reopen it.

 

Using a Separate Thread for the Service
You can use the service's built-in service thread for simple services. For other types of services, you probably want to spawn a separate thread to execute the service's code. Whether or not you do this depends largely on what the service does. If, for example, the service is expected to handle multiple client requests, then you may want a separate thread for each client request.

 

You should also use a separate thread for your service if your service performs lengthy startup tasks. The SCM allows two minutes for a service to start. The SCM instructs the service to start and then waits for a response from the service. If the response doesn't come back within two minutes, NT assumes that the service is hung and kills the service process. If your startup tasks will take longer than two minutes, you should spawn a separate thread to perform the startup tasks. This frees the service thread to handle service requests from the SCM.

 

Figures 5 and 6 show a modified beep service called BeepSrvcThread. This version of the beep services uses a separate thread to handle the service's code.

 

#ifndef BeepServiceThreadUH
#define BeepServiceThreadUH
 
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <SvcMgr.hpp>
 
class TBeepThread : public TThread {
   public:
     virtual __fastcall void Execute();
     __fastcall TBeepThread(bool CreateSuspended);
};
 
class TBeepServiceThread : public TService
{
__published:     // IDE-managed Components
   void __fastcall BeepServiceThreadStart(
    TService *Sender, bool &Started);
   void __fastcall BeepServiceThreadStop(
    TService *Sender, bool &Stopped);
   void __fastcall BeepServiceThreadPause(
    TService *Sender, bool &Paused);
   void __fastcall BeepServiceThreadContinue(
    TService *Sender, bool &Continued);
private:         // User declarations
  TBeepThread* BeepThread;
public:          // User declarations
   __fastcall TBeepServiceThread(TComponent* Owner);
  PServiceController __fastcall GetServiceController(void);
   friend void __stdcall ServiceController(unsigned CtrlCode);
};
 
extern PACKAGE TBeepServiceThread *BeepServiceThread;
 
#endif
Figure 5: The header for the BeepSrvcThread program.
 

#include "BeepServiceThreadU.h"
 
#pragma package(smart_init)
#pragma resource "*.dfm"
 
TBeepServiceThread *BeepServiceThread;
 
// TBeepThread implementation
__fastcall TBeepThread::TBeepThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
 
void __fastcall TBeepThread::Execute()
{
   while (!Terminated) {
    MessageBeep(0);
    Sleep(1000);
  }
}
 
// TBeepServiceThread implementation
__fastcall TBeepServiceThread::TBeepServiceThread(TComponent* Owner)
        : TService(Owner)
{
}
 
PServiceController __fastcall
  TBeepServiceThread::GetServiceController(void)
{
         return (PServiceController) ServiceController;
}
 
void __stdcall ServiceController(unsigned CtrlCode)
{
        BeepServiceThread->Controller(CtrlCode);
}
 
void __fastcall TBeepServiceThread::BeepServiceThreadStart(
      TService *Sender, bool &Started)
{
  BeepThread = new TBeepThread(false);
  Started = true;
}
 
void __fastcall TBeepServiceThread::BeepServiceThreadStop(
  TService *Sender, bool &Stopped)<$I~services;threads><$I~threads;services><$I~Windows NT;services;threads><$I~BeepService program example;threads><$I~Windows NT;services;BeepService program example>
{
  BeepThread->Terminate();
  Stopped = true;
}
void __fastcall TBeepServiceThread::BeepServiceThreadPause(
   TService *Sender, bool &Paused)
{
  BeepThread->Suspend();
  Paused = true;
}
 
void __fastcall TBeepServiceThread::BeepServiceThreadContinue(
  TService *Sender, bool &Continued)
{
  BeepThread->Resume();
  Continued = true;
}
Figure 6: The source code for the BeepSrvcThread program.
 

There are two important differences between this service and the BeepService program presented earlier:

1)        This service does not use the service's OnExecute event.

2)        This service provides event handlers for the OnStart, OnStop, OnPause, and OnContinue events.

 

Since I am not using the service's built-in service thread, I must take responsibility for specifically starting, stopping, suspending, and resuming my thread when the SCM sends the start, stop, pause, or continue requests. For example, the OnPause and OnContinue event handlers look like Figure 7.

 

void __fastcall TBeepServiceThread::BeepServiceThreadPause(
  TService *Sender, bool &Paused)
{
  BeepThread->Suspend();
  Paused = true;
}
 
void __fastcall TBeepServiceThread::BeepServiceThreadContinue(
  TService *Sender, bool &Continued)
{
  BeepThread->Resume();
  Continued = true;
}
Figure 7: The OnPause and OnContinue event handlers.
 

Notice that I set the Paused parameter to true in the OnPause event handler after I suspended the thread. If I don't set Paused to true, Windows can't pause the thread. You may want to set Paused to false if your service is in a state where pausing the service will cause catastrophic results. Be aware, though, that if you do this, Windows will report an error if it cannot pause the service within three seconds. This same discussion applies to stopping a service or to continuing a paused service.

 

There isn't much more to say on the subject of using separate threads for your services. Actually, that statement is a bit misleading. The reality is that there is so much more to say on this subject that it would take dozens of pages to adequately cover it. I realize that this short example doesn't give you much information on the nitty-gritty details of dealing with threads in services. The reason is simply that services are so varied that I really can't anticipate what issues you might encounter with a particular service you might write.

 

Writing Interactive Services
An interactive service is one that can interact with the Windows desktop. Generally, this means that the service can display a dialog box to the user. Often the dialog box allows the user to configure some aspect of how the service operates. The example program for the remainder of the subjects discussed in this series is called PingService. The complete project is available for download; see end of article for details. The service has the following functionality and features:

Pings a remote machine at periodic intervals.
If the remote machine doesn't respond to the ping, the service takes one of two actions depending on the service's configuration:
Logs the failure to the system Event Log.
Sends e-mail to a specified address notifying you of the failure.
When the remote machine comes back up, the event is logged (or e-mail sent).
A configuration dialog box allows you to set the remote machine name, the ping interval, the notification type, the e-mail address, and the e-mail host to use for e-mail notification.
Configuration data is stored in the registry to be reloaded the next time the service starts.
Displays an icon representing the service in the system tray.
Displays a pop-up menu for the tray icon.
 

The service has three units: The first is the service's main unit, the second is the thread that performs the ping, and the third is the service's configuration form. Figures 8 through 13 show each of the units and their headers.

 

#ifndef PingServiceUH
#define PingServiceUH
 
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <SvcMgr.hpp>
#include <Controls.hpp>
#include <Menus.hpp>
#include <ExtCtrls.hpp>
#include "PingThreadU.h"
 
class TBCB4UnlPing : public TService
{
__published:     // IDE-managed Components
   void __fastcall BCB4UnlPingStart(TService *Sender, bool &Started);
   void __fastcall BCB4UnlPingStop(TService *Sender, bool &Stopped);
   void __fastcall BCB4UnlPingExecute(TService *Sender);
private:         // User declarations
public:          // User declarations
  TPingThread* PingThread;
   __fastcall TBCB4UnlPing(TComponent* Owner);
  PServiceController __fastcall GetServiceController(void);
protected:
   friend void __stdcall ServiceController(unsigned CtrlCode);
};
 
extern PACKAGE TBCB4UnlPing *BCB4UnlPing;
 
#endif
Figure 8: The header for the PingService unit.
 

#include "PingServiceU.h"
#include "ConfigU.h"
#include "PingThreadU.h"
 
#pragma package(smart_init)
#pragma resource "*.dfm"
 
TBCB4UnlPing *BCB4UnlPing;
 
__fastcall TBCB4UnlPing::TBCB4UnlPing(TComponent* Owner)
        : TService(Owner)
{
}
 
PServiceController __fastcall TBCB4UnlPing::GetServiceController(void)
{
         return (PServiceController) ServiceController;
}
void __stdcall ServiceController(unsigned CtrlCode)
{
        BCB4UnlPing->Controller(CtrlCode);
}
 
void __fastcall TBCB4UnlPing::BCB4UnlPingStart(TService *Sender,
       bool &Started)
{
  PingThread = new TPingThread(false);
  ConfigForm->LoadConfiguration();
  Started = true;
}
 
void __fastcall TBCB4UnlPing::BCB4UnlPingStop(TService *Sender,
       bool &Stopped)
{
  PingThread->Terminate();
  Stopped = true;
}
 
void __fastcall TBCB4UnlPing::BCB4UnlPingExecute(TService *Sender)
{
   while (!Terminated)
    ServiceThread->ProcessRequests(false);
}
Figure 9: The source code for the PingService unit.
 

#ifndef PingThreadUH
#define PingThreadUH
#include <NMsmtp.hpp>
#include <Psock.hpp>
 
class TPingThread : public TThread {
   private:
     bool ServerDownReported;
     int IPAddress;
     bool Ping();
     void SendMail(bool ServerUp);
   public:
    String IPString;
    String EMailToAddress;
    String EMailFromAddress;
     String EMailHostAddress;
     bool LogError;
     int PingInterval;
     void MakeIPAddress();
     virtual __fastcall void Execute();
     __fastcall TPingThread(bool CreateSuspended);
};
 
#endif
Figure 10: The header for the TPingThread class.
 

#include <vcl.h>
#pragma hdrstop
 
extern "C" {
#include "ipexport.h""
#include "icmpapi.h"
}
#include "PingThreadU.h"
#include "PingServiceU.h"
 
#pragma package(smart_init)
 
const char* statusMsg = "The server at %s is %s";
 
__fastcall TPingThread::TPingThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
  LogError = true;
  PingInterval = 5000;
  IPAddress = -1;
  ServerDownReported = false;
  FreeOnTerminate = true;
}
 
void __fastcall TPingThread::Execute()
{
   while (!Terminated) {
    Sleep(PingInterval);
     if (IPString != "") {
       bool goodPing = Ping();
       if (!goodPing && !ServerDownReported) {
         if (LogError) {
          String Msg = String().sprintf(
            statusMsg, IPString.c_str(), "down.");
          BCB4UnlPing->LogMessage(
             Msg, EVENTLOG_INFORMATION_TYPE, 0, 0);
        }
         else
          SendMail(false);
        ServerDownReported = true;
      }
       if (goodPing && ServerDownReported) {
        ServerDownReported = false;
         if (LogError) {
          String Msg = String().sprintf(
            statusMsg, IPString.c_str(), "up.");
          BCB4UnlPing->LogMessage(
            Msg, EVENTLOG_INFORMATION_TYPE, 0, 0);
        }
         else
          SendMail(true);
      }<$I~services;interactive><$I~interactive services;(Windows NT)><$I~Windows NT;services;interactive>
    }
  }<$I~TPingThread Class (listing 6.8)><$I~listings;TPingThread Class><$I~PingService program example><$I~Windows NT;services;PingService program example>
}
 
bool TPingThread::Ping()
{
   bool result = true;
  HANDLE hIcmp = IcmpCreateFile();
   if (!hIcmp) {
    BCB4UnlPing->LogMessage("Unable to load ICMP.DLL. The "
      "service is terminating.", EVENTLOG_ERROR_TYPE, 0, 0);
    Terminate();
     return false;
  }
   int size = sizeof(icmp_echo_reply) + 8;
   char* buff = new char[size];
  DWORD res = IcmpSendEcho(
    hIcmp, IPAddress, 0, 0, 0, buff, size, 1000);
   if (!res)
     // timed out
    result = false;
   else {
    icmp_echo_reply reply;
    memcpy(&reply, buff, sizeof(reply));
     delete[] buff;
     if (reply.Status> 0)
       // error
      result = false;
  }
  IcmpCloseHandle(hIcmp);
   return result;
}
 
void TPingThread::SendMail(bool ServerUp)
{
  TNMSMTP* SMTP = new TNMSMTP(0);
   try {
    SMTP->PostMessage->FromName = "BCB 4 Unleashed Ping Service";
    SMTP->PostMessage->ToAddress->Add(EMailToAddress);
    SMTP->PostMessage->FromAddress = EMailFromAddress;
    SMTP->Host = EMailHostAddress;
    SMTP->PostMessage->Subject = "Server Alert";
    String Msg;
     if (ServerUp)
       Msg.sprintf(statusMsg, IPString.c_str(), "up.");
     else
      Msg.sprintf(statusMsg, IPString.c_str(), "down.");
    SMTP->PostMessage->Body->Add(Msg);
    SMTP->Connect();
    SMTP->SendMail();
  }
   catch (Exception& E) {
    BCB4UnlPing->LogMessage("Unable to send e-mail: " +
      E.Message, EVENTLOG_ERROR_TYPE, 0, 0);<$I~services;interactive><$I~interactive services;(Windows NT)><$I~Windows NT;services;interactive>
     delete SMTP;<$I~TPingThread Class (listing 6.8)><$I~listings;TPingThread Class><$I~PingService program example><$I~Windows NT;services;PingService program example>
     return;
  }
   delete SMTP;
}
 
void TPingThread::MakeIPAddress()
{
   // Intialize Winsock.dll
  WSADATA wsaData;
  memset(&wsaData, 0, sizeof(WSADATA));
   #pragma warn -prc
  Word lowVersion = MAKEWORD(2, 0);
   int res = WSAStartup(lowVersion, &wsaData);
   if (res) {
    BCB4UnlPing->LogMessage("Unable to load Winsock. The "
      "service is terminating.", EVENTLOG_ERROR_TYPE, 0, 0);
    Terminate();
     return;
  }
   // Strip spaces from the IPString text
   for (int i=IPString.Length();i>0;i--) {
     if (IPString[i] == ' ')
      IPString.Delete(i, 1);
  }
   // Is the address in IP format?
  IPAddress = inet_addr(IPString.c_str());
   if (IPAddress == -1) {
     // Is the address a host name?
    hostent* he =
      gethostbyname(IPString.c_str());
     if (!he) {
       int error = WSAGetLastError();
      BCB4UnlPing->LogMessage("Winsock error: " + String(error),
        EVENTLOG_ERROR_TYPE, 0, 0);
      Terminate();
       return;
    }
     else
      memcpy(&IPAddress,
        he->h_addr_list[0], sizeof(int));
  }
  WSACleanup();<$I~TPingThread Class (listing 6.8)><$I~listings;TPingThread Class><$I~PingService program example><$I~Windows NT;services;PingService program example>
}
Figure 11: The source code for the TPingThread class.
 

#ifndef ConfigUH
#define ConfigUH
 
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Registry.hpp>
#include <Menus.hpp>
#include <ExtCtrls.hpp>
#include <shellapi.h>
 
#define WM_PINGICONMESSAGE WM_USER + 1
 
 
class TConfigForm : public TForm
{
__published:     // IDE-managed Components
  TLabel *Label1;
  TLabel *Label2;
  TEdit *ServerEdit;
  TEdit *FrequencyEdit;
  TGroupBox *GroupBox1;
  TRadioButton *LogRadioBtn;
  TRadioButton *EmailRadioBtn;
  TEdit *EmailToEdit;
  TLabel *Label3;
  TButton *OKBtn;
  TLabel *Label4;
  TLabel *Label5;
  TEdit *EmailFromEdit;
  TEdit *EmailHostEdit;
  TPopupMenu *PopupMenu;
  TMenuItem *Configure;
  TTimer *Timer;
   void __fastcall OKBtnClick(TObject *Sender);
   void __fastcall FormShow(TObject *Sender);
   void __fastcall RadioButtonClick(TObject *Sender);
   void __fastcall FormDestroy(TObject *Sender);
   void __fastcall ConfigureClick(TObject *Sender);
   void __fastcall TimerTimer(TObject *Sender);
private:         // User declarations
  NOTIFYICONDATA IconData;
   void SaveConfiguration();
   void __fastcall PingIconMessage(TMessage& Msg);
     void AddIcon();
public:         // User declarations
   __fastcall TConfigForm(TComponent* Owner);
   void LoadConfiguration();
  BEGIN_MESSAGE_MAP
    VCL_MESSAGE_HANDLER(WM_PINGICONMESSAGE, TMessage, PingIconMessage)
  END_MESSAGE_MAP(TForm)
};
 
extern PACKAGE TConfigForm *ConfigForm;
 
#endif
Figure 12: The header for the Configuration form.
 

#include
#pragma hdrstop
 
#include "ConfigU.h"
#include "PingServiceU.h"
 
#pragma package(smart_init)
#pragma resource "*.dfm"
const char* regKey =
  "System\\CurrentControlSet\\Services\\BCB4UnlPing\\Parameters";
TConfigForm *ConfigForm;
 
__fastcall TConfigForm::TConfigForm(TComponent* Owner)
  : TForm(Owner)
{
}
 
void __fastcall TConfigForm::OKBtnClick(TObject *Sender)
{
  BCB4UnlPing->PingThread->IPString = ServerEdit->Text;
   if (BCB4UnlPing->PingThread->IPString != "")
    BCB4UnlPing->PingThread->MakeIPAddress();
  BCB4UnlPing->PingThread->PingInterval =
    FrequencyEdit->Text.ToIntDef(5000);
  BCB4UnlPing->PingThread->EMailFromAddress = EmailFromEdit->Text;
  BCB4UnlPing->PingThread->EMailToAddress = EmailToEdit->Text;
  BCB4UnlPing->PingThread->EMailHostAddress = EmailHostEdit->Text;
  BCB4UnlPing->PingThread->LogError = LogRadioBtn->Checked;
  SaveConfiguration();
  Close();
}
 
void __fastcall TConfigForm::FormShow(TObject *Sender)
{
  SetForegroundWindow(Handle);
  RadioButtonClick(0);
  ServerEdit->Text = BCB4UnlPing->PingThread->IPString;
  FrequencyEdit->Text = BCB4UnlPing->PingThread->PingInterval;
  EmailFromEdit->Text = BCB4UnlPing->PingThread->EMailFromAddress;
  EmailToEdit->Text = BCB4UnlPing->PingThread->EMailToAddress;
  EmailHostEdit->Text = BCB4UnlPing->PingThread->EMailHostAddress;
  LogRadioBtn->Checked = BCB4UnlPing->PingThread->LogError;
  EmailRadioBtn->Checked = !BCB4UnlPing->PingThread->LogError;
}
 
void __fastcall TConfigForm::RadioButtonClick(TObject *Sender)
{
   if (LogRadioBtn->Checked) {
    EmailFromEdit->Enabled = false;
    EmailToEdit->Enabled = false;
    EmailHostEdit->Enabled = false;
    EmailFromEdit->Color = clBtnFace;
    EmailToEdit->Color = clBtnFace;
    EmailHostEdit->Color = clBtnFace;
  }
   else {
    EmailFromEdit->Enabled = true;
    EmailToEdit->Enabled = true;
    EmailHostEdit->Enabled = true;
    EmailFromEdit->Color = clWindow;
    EmailToEdit->Color = clWindow;
    EmailHostEdit->Color = clWindow;
  }
}
 
void TConfigForm::LoadConfiguration()
{
  TRegistry* reg = new TRegistry;
  reg->RootKey = HKEY_LOCAL_MACHINE;
  reg->OpenKey(regKey, true);
   if (reg->ValueExists("IPAddress")) {
    BCB4UnlPing->PingThread->IPString = reg->ReadString("IPAddress");
    BCB4UnlPing->PingThread->MakeIPAddress();
    BCB4UnlPing->PingThread->EMailToAddress =
      reg->ReadString("EMailTo");
    BCB4UnlPing->PingThread->EMailFromAddress =
      reg->ReadString("EMailFrom");
    BCB4UnlPing->PingThread->EMailHostAddress =
      reg->ReadString("EMailHost");
    BCB4UnlPing->PingThread->PingInterval =
      reg->ReadInteger("Interval");
    BCB4UnlPing->PingThread->LogError = reg->ReadBool("LogError");
  }
   delete reg;
}
 
void TConfigForm::SaveConfiguration()
{
  TRegistry* reg = new TRegistry;
  reg->RootKey = HKEY_LOCAL_MACHINE;
  reg->OpenKey(regKey, true);
  reg->WriteString("IPAddress", BCB4UnlPing->PingThread->IPString);
  reg->WriteString("EMailTo", BCB4UnlPing->PingThread->EMailToAddress);
  reg->WriteString("EMailFrom",
    BCB4UnlPing->PingThread->EMailFromAddress);
  reg->WriteString("EMailHost",
    BCB4UnlPing->PingThread->EMailHostAddress);
  reg->WriteInteger("Interval", BCB4UnlPing->PingThread->PingInterval);
  reg->WriteBool("LogError", BCB4UnlPing->PingThread->LogError);
   delete reg;
}
 
void __fastcall TConfigForm::PingIconMessage(TMessage& Msg)
{
   if (Msg.LParam == WM_RBUTTONDOWN) {
    POINT p;
    GetCursorPos(&p);
    SetForegroundWindow(Handle);
    PopupMenu->Popup(p.x, p.y);
  }
   if (Msg.LParam == WM_LBUTTONDBLCLK) {
    BCB4UnlPing->PingThread->Suspend();
    ShowModal();
    BCB4UnlPing->PingThread->Resume();
  }
}
 
void __fastcall TConfigForm::FormDestroy(TObject *Sender)
{
  Shell_NotifyIcon(NIM_DELETE, &IconData);
}
 
void __fastcall TConfigForm::ConfigureClick(TObject *Sender)
{
  BCB4UnlPing->PingThread->Suspend();
  ShowModal();
  BCB4UnlPing->PingThread->Resume();
}
 
void __fastcall TConfigForm::TimerTimer(TObject *Sender)
{
   if (FindWindow("Progman", 0)) {
    AddIcon();
    Timer->Enabled = false;
  }
}
 
void TConfigForm::AddIcon()
{
  IconData.cbSize = sizeof(NOTIFYICONDATA);
  IconData.hWnd = Handle;
  IconData.uID = 1;
  IconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
  IconData.uCallbackMessage = WM_PINGICONMESSAGE;
  IconData.hIcon = LoadIcon(HInstance, "SERVICEICON1");
  strcpy(IconData.szTip, "Unleashed Ping");
  Shell_NotifyIcon(NIM_ADD, &IconData);
}
Figure 13: The source code for the Configuration form.
 

I won't discuss the code that performs the ping and the code that converts the server host name or IP address string to a 32-bit IP address. I also won't explain the code that sends the notification e-mails, as that code is simple enough to understand without further explanation.

 

The design of PingService is as follows: The service unit contains very little code. It contains the minimum amount of code needed to spawn the service's secondary thread, and to handle the service's stop and start commands. Because this is an interactive service, the Interactive property is set to true at design time. I have also set the AllowPause property to false in order to avoid the complications that come with a paused service that displays a tray icon. There's not much benefit in pausing PingService.

 

The main processing for the service is done by the TPingThread class. This thread sits in a loop and pings a remote machine at an interval determined by the configuration parameters. The default ping interval is five seconds (5,000 milliseconds). If the ping fails, the thread either logs the failure in the system Event Log or sends a notification e-mail, depending on the service's configuration parameters. When the remote machine comes back up, the thread either logs the event or sends a notification e-mail. Note that the service's e-mail feature assumes the service is being run on a network server that has e-mail access. No attempt is made to connect to the network via Dial-Up Networking.

 

The configuration form unit contains a fair amount of code. The code in this unit manages the service's configuration settings, the service's icon in the system tray, and the pop-up menu for the tray icon. The form's Visible property is set to false at design time and the form is only shown when needed. The form is auto-created and is not destroyed until the service terminates. I'll explain later why it is important to keep the form around, even when not directly being used.

 

Note: This service is intended to be used as the basis for a network monitoring system. In its present incarnation, the service only pings a single remote machine. In a real-world implementation, you would likely provide a list of IP addresses that the service could ping. In addition, PingService only pings the remote machine's IP address to ensure that the machine is running. It doesn't attempt to ping a particular server on the machine (such as a Web server or an FTP server). If you want to extend this service to add those features, you need to spend some time experimenting with the technologies involved. I encourage you to check the Borland C++Builder newsgroups for information on this subject (see the cppbuilder.internet newsgroup in particular).

 

Understanding the Service's Ping Thread
There a few aspects of the ping thread that I want to discuss before getting on to items specific to interactive services. Most of the code I discuss is found in the thread's Execute method. All of the service's configuration parameters are reflected in the TPingThread class' public data members:

 

public:
  String IPString;
  String EMailToAddress;
  String EMailFromAddress;
  String EMailHostAddress;
   bool LogError;
   int PingInterval;
 

These data members correspond directly to the fields on the configuration dialog box. I probably should have made these variables properties rather than public data members, but I wanted to keep things simple. These data members are public so the configuration form can update their values when the configuration changes.

 

The PingThread's Execute method sleeps the designated amount of time and then attempts to ping the remote machine:

 

while (!Terminated) {
  Sleep(PingInterval);
   if (IPString != "") {
     bool goodPing = Ping();
     // code omitted
  }
}
 

Notice that the thread first ensures that the IPString data member is not empty. This check is necessary for obvious reasons. When the service is first installed, for example, the IP address and other parameters have not yet been set. If IPString contains text, then the Ping function is called to ping the remote machine.

 

Note: The term remote machine gets tiresome after a while. Since PingService is intended to be used as a networking monitoring tool, I will simply use the term server from this point forward.

 

The next few lines of code in the Execute function check to see if the ping succeeded and, if not, whether the failure has already been reported (see Figure 14).

 

if (!goodPing && !ServerDownReported) {
   if (LogError) {
    String Msg = String().sprintf(
      statusMsg, IPString.c_str(), "down.");
    BCB4UnlPing->LogMessage(
      Msg, EVENTLOG_INFORMATION_TYPE, 0, 0);
  }
   else
    SendMail(false);
    ServerDownReported = true;
  }
}
Figure 14: These lines check to see if the ping succeeded and, if not, whether the failure has been reported.
 

A private variable called ServerDownReported is used internally to determine whether the failure has been reported. Had I not written the service this way, the failure would be continuously reported every five seconds (the default ping interval). I only want the failure to be reported once. The LogError data member indicates whether the user has configured the service to log the error or to send e-mail. If LogError is true, the failure is written to the NT Event Log by calling the service's LogMessage method. (I discuss event logging in more detail in Part III of this series.) If LogError is false, I call the SendMail function of the ping thread to send e-mail notification of the failure.

 

A network administrator will naturally want to know if the server comes back up again. The remaining code in the Execute method reports the server's up status:

 

if (goodPing && ServerDownReported) {
  ServerDownReported = false;
   if (LogError) {
    String Msg = String().sprintf(
      statusMsg, IPString.c_str(), "up.");
    BCB4UnlPing->LogMessage(
      Msg, EVENTLOG_INFORMATION_TYPE, 0, 0);
  }
   else
    SendMail(true);
}
 

As with reporting the failure, the server's coming back online will only be reported once.

'언어 > C++ Builder' 카테고리의 다른 글

JVC JvStringGrid 사용하기  (0) 2010.08.12
C++ 유용한 사이트  (0) 2010.08.09
Enum, Set  (0) 2009.09.09
쓰레드 기초.  (0) 2009.09.07
쓰레드를 이용한 채팅 서버 만들기.  (0) 2009.09.04