오후 10:38 99-12-28
조경민 bro@shinbiro.com
Technical Articles/Windows Platform/Base Services
----------------------------------------------------------------

Creating a Simple Windows NT Service in C++
C++로 간단한 윈도우NT 서비스 만들기
Nigel Thompson
Microsoft Developer Network Technology Group

November 1995

Click to open or copy the files for the NTService sample application.

Click to open or copy the files for the NTServCpl sample application.

Click to open or copy the files for the NTServCtrl sample application.

들어가기
Abstract

이 기사는 어떻게 간단한 마이크로소프트 윈도우 NT 서비스를 마이크로
소프트 C++을 이용해서 만드는지 설명하고 있다. 서비스는 하나의 C++클래
스로 만들어 졌는데 이는 당신의 서비스와 운영체제간의 간단한 인터페이스
를 제공한다. 이 클래스를 이용해서 당신은 간단히 몇개의 virtual함수
들을 오버라이드 함으로써 쓸수 있다. 아래 세개의 애플리케이션들이
있다. ( MSDN에서 샘플을 다운 받아 보세요 )

This article describes how to create simple MicrosoftR Windows
NT™ services using Microsoft Visual C++™. The services are
created using a single C++ class that provides a simple
interface between your service and the operating system.
Using this class approach, your own implementation is
simply a matter of overriding a few virtual functions in
the base class. Three sample applications accompany this
article:

NTService 샘플은 간단히 Window NT 서비스를 이 기사에 있는
대로의 메소드를 사용함으로써 만들어졌다.
The NTService sample is a simple Windows NT service built
using the method described in this article.

NTServCpl 샘플은 컨트롤 패널 애플릿(not in java)으로
NT서비스의 서비스 상태를 조정할수 있다. 이 애플리케이션은
"비주얼C++로 Win32 컨트롤 패널 애플릿을 만들기"라는 테크니컬
기사에 있는 것으로 만들어진것이다.
The NTServCpl sample is a Control Panel applet that
controls the NTService service. This application was
built using the techniques described in the "Creating
Win32 Control Panel Applets with Visual C++" technical
article in the MSDN Library.


NTServCtrl 샘플 애클리케이션은 stand-alone 애플리케이션
의 한 예로 이것은 NT 서비스에 대해 제어 및 모니터를 할 수 있다.
The NTServCtrl sample application is an example of a
stand-alone application that can be used to control
and monitor a Windows NT service.


소개
Introduction

윈NT에서 서비스라는 것은 컴퓨터가 운영체제에 의해 실행중이거나
아니거나 동작하는 프로그램을 의미하는데 이것은 유저가 로그온할
필요가 없다. 서비스는 사용자 독립적인 작업 즉, 디렉토리 리플리케
이션, 프로세스 모니터링, 또는 네트웍상 다른 머신에게 인터넷 HTTP
프로토콜같은것으로 서비스하기등을 구현할때 필요하다.
A service in MicrosoftR Windows NT™ is a program that runs
whenever the computer is running the operating system. It
does not require a user to be logged on. Services are needed
to perform user-independent tasks such as directory replication,
process monitoring, or services to other machines on a network,
such as support for the Internet HTTP protocol.

윈NT용 서비스를 만들기는 특별히 어려운것은 없다.
그러나 서비스를 디버깅하는것은 조금은 어렵다.
그래서 난 마이크로 소프트 C++을 이용해서 C++로 나의 애플리케이
션을 만들기를 좋아한다. 대부분읜 윈도우 NT 서비스 샘플은
C로 되어 있다. 그래서 난 C++로 짜면
다하고 나니까, C++로는 NT서비스를 쉽게 만들수 있었다.
난 당신이 하려는 작업에 적합하게 쓰이도록 기초 클래스를 작성
하였다.
Creating a service for Windows NT is not particularly hard.
Debugging a service is, however, a little more difficult.
For my own work I prefer to create my applications in C++
using Microsoft Visual C++™. Most Windows NT service
samples are in C, so I thought it would be interesting
to see if I could create a C++ class to perform the
rudimentary functions of a Windows NT service. As it
turns out, one can create Windows NT services in C++
quite simply. The base class I developed for this should
be an adequate starting point for your own work.

서비스만들기는 서비스코드를 꽤 많이 수반하고 있다. 추가적으로
당신은 다음을 하기위해서 코드를 짜야한다.
Creating a service involves a bit more that just the service
code. Additionally, you must write code to:

경고나 에러들을 시스템이나 애플리케이션 로그에 기록해야한다.
당신은 전형적으로 그런 출력을 스크린으로 띄울수 없는데 그이유는
사용자가 로그온 하지 않은 상황일수도 있기 때문이다.
Report warnings and errors in the system or application logs.
You typically can't use output to the screen since there may
be no user logged on.

다른 분리된 애플리케이션이나 컨트롤 애플릿을 통해서
서비스를 제어해야 한다.
Control the service through either a separate application or
a Control Panel applet. This involves implementing a
communication mechanism for your service.


시스템에서 서비스를 설치하거나 제거하기
Install and remove the service from the system.

대부분의 다른 서비스 예제들은 하나의 프로그램이 서비스를
설치하거나 삭제하는데 사용한다. 나는 그런 함수들은 서비스
자체에다가 놓아서 당신이 단지 하나의 .EXE만을 배포하면
된다. 당신은 직접 커맨드라인으로 서비스 애플리케이션을
동작시킬수 있으며 설치나 삭제(uninstall)또는 자신의 버전
정보등을 보고할 수 있다. NTService 예제는 다음과 같은
커맨드 라인 인자를 갖고 있다.
Most other service examples use one program to install the
service and another to remove it. I built these functions
into the service itself so you have only one .EXE to
distribute. You can run the service application directly
from the command line and ask it to install, uninstall,
or report its version information. The NTService sample
supports the following command-line arguments:

-v는 자기 서비스의 이름이나 버전 숫자를 출력한다.
-v, which reports the name and version number of the service

-i는 서비스를 설치한다.
-i, which installs the service

-u는 서비스를 삭제한다.
-u, which removes the service


기본적으로, 시스템이 서비스를 시작할때에는 커맨드라인 인자를
쓰지는 않는다.
By default, when the system starts the service there will
be no command-line arguments passed to it.

애플리케이션 프레임웍 만들기
Creating the Application Framework

나는 오랫동안 MFC를 기반으로 애플리케이션을 만들어 왔다.
내가 초기에 나의 윈NT서비스를 위해서 세팅을 해놓을때 나는
VC++애플리케이션 위자드로 시작했고, SDI/MFC 애플리케이션을
만들었다. 난 다큐먼트와 비클래스들 그리고 아이콘을 지우려고
했고 게속해서 그 프레임워크를 지우려고 했다. 결국에는, 그 당시
당신은 모든 것들을 지우게 되었고 메인 윈도우도 지웠다.
(우리는 윈도우도 필요치 않기때문에) 아무것도 남지 않았다.
그래서 나는 다시 애플리케이션 위자드로 돌아가서 콘솔 애플리
케이션 프로젝트를 만들었고 하나의 소스파일안에는 메인 엔트르
포인트 함수가 들어 있었다. 나는 NTServApp.cpp를 이 파일로
만들었다. 나는 종종 cpp확장자를 c보다는 좋아하는데, 왜냐면
난 전체 프로젝트를 몽땅 C로 하기보단 C++을 사용하여 만들길
원하기 때문이다. 우리는 코드의 수행에 대해서 후에 볼것이다.
I have been creating applications based on the Microsoft
Foundation Class Library (MFC) for too long. When I initially
set out to build my Windows NT service, I started with the
Visual C++ AppWizard and created an SDI/MFC application. I
intended to remove the document and view classes, icons, and
so on and just leave the framework. As it turns out, by the
time you've removed all that stuff and the main window (since
we can't have one), there isn't anything left. Very silly.
So I went back to AppWizard and created a Console Application
project with a single source file that would contain the main
entry point function. I called this file NTServApp.cpp. I used
the cpp extension rather than just c because I wanted to write
the entire project using C++ rather than straight C. We'll look
at the implementation of the code in this file later.

난 나의 스비스를 C++클래스로 만들길 원하기 떄문에 난 NTService.h
와 NTService.cpp파일을 만들었다. 그래서 난 CNTService 기초 클래스
를 구현하였다. 또한 난 MyService.h와 MyService.cpp파일을 만들었는데
이것들은 내가 나의 서비스 클래스가(CMyService) CNTService로부터
상속된것을 구현한것이다. 다시, 코드는 조금 있다가 보여주겠다.
Since I wanted to build my service from a C++ class, I created
the NTService.h and NTService.cpp files in which I would
implement the CNTService base class. I also created the
MyService.h and MyService.cpp files in which I would implement
my own service class (CMyService) derived from CNTService.
Again, well look at the code a bit later.

난 새로운 프로젝트를 시작할때, 가능한 빨리 어떤것이든 동작하는
것을 보길 바라므로(나도 그렇다) 난 처음것은 내 서비스가
시스템 애플리케이션 로그 파일안에 몇개의 엔트리를 만들게끔하기로
결심하였다. 이런 엔트리를 만드는 메카니즘을 구현함에 있어 난
서비스가 시작할때나 멈췄을때 등을 추적하고 싶었다. 또 난
서비스에서 일어날수 있는 어떤 에러들이든 기록하고 싶었다.
로그 엔트리를 만드는것은 꽤나 내가 생각했던거 보다 어려웠다.
When I start a new project, I like to get something working
as soon as possible, so I decided that the first thing my
service should do is make some entries in the system's
application log file. Having implemented a mechanism for
making these entries, I'd be able to track when the service
was started, stopped, and so on; I'd also have a way to
record any errors that might occur in the service. Making
log entries turned out to be much more complicated than I
thought.

로그 엔트리를 만들기
Making Log Entries
난 로그 파일은 운영체제에 속해있다는것을 알고 있다. 거기에는
몇게의 애플리케이션 프로그래밍 인터페이스(API)를 지원하기 위해
만들어진 엔트리들이 있었다. 그래서 난 내가 신뢰하는 MSDN CD를
꺼내서 ReportEvent 함수를 찾을때까지 돌아댕겼다. 이제는 만일
당신이 이런 것들에 대해서 알지 못한다면 당신은 아마도 이런 함수
는 아마도 로그 파일에서 당신이 엔트리를 만들고 싶을때나
메세지에 대한 문장들을 엏을때 필요할거라고 알면 된다.
자, 그와 같이 한다면, 지역화(국제화)된 에러 메세지를 암시하는데
이 함수들이 메세지 ID를 갖고 당신이 제공한 메세지 테이블안에서
메세지를 찾게된다. 그래서 문제는 어떤 메세지를 당신이 로그에
넣을것인가라기 보다 당신의 애플리케이션에 그런 메세지를
어떻게 추가할것인가 이다. 이기에 따라하기 안내가 있다.
I figured that since the log files were a part of the
operating system, there would be some application programming
interface (API) support for making entries into them. So I
broke out my trusty MSDN CD and dug around until I found the
ReportEvent function. Now if you don't know about this stuff,
you'd probably think that this function would need to know in
which log file you want to make the entry, and the text of the
message you want to insert. Well, that's sort of what it does,
but to simplify internationalization of error messages, this
function takes a message ID and looks up the message in a
message table you provide. So the problem is not so much
what message you want to put in the log, as how to add these
messages to your application. Here's a step-by-step guide:

텍스트 파일을 확장자를 .NC로 메세지들의 설명이 있는 것을만들어라
난 나의 NTServMsg.mc를 만들었다. 이 포맷은 매우 특화된것이고
Win32R 소프트 개발 킷(SDK)툴 유저 가이드 의"컴파일된 메세지들"
섹션안에 있다.
Create a text file with the extension .MC containing the
descriptions of the messages. I called mine NTServMsg.mc.
The format is very specific and is covered in the "Compiling
Messages" section of the Win32R Software Development Kit
(SDK) Tools User's Guide.

메세지 컴파일러(MC.EXE)를 당신의 소스 파일에 대해서 실행하는데
기본적으로 출력파일은 MSG00001.BIN이라는것이 나오게 된다.
컴파일러는 또한 헤더파일( 나의 경우 NTServMsg.h)을 생성하고
.RC파일 (NTServMsg.rc)를 만든다. 당신은 이런 작업을 .MC파일이
당신의 프로젝트 안에서 바뀌게 된다면 반족해서 해야 할 것이다.
그래서 당신의 VC++메뉴가 그것을 할 수 있게 툴 엔트리에 추가하는
것이 편리하다.
Run the message compiler (MC.EXE) against your source file,
which by default creates an output file called MSG00001.BIN.
The compiler also creates a header file (in my case NTServMsg.h)
and an .RC file (NTServMsg.rc). You need to repeat this step any
time you change the .MC file in your project, so it's handy to
add a tool entry in your Visual C++ menu to do this.


.RC파일을 당신의 프로젝트를 위해서 만들고 #include에다가
WINDOWS.h파일을 놓는다. .RC파일은 메세지 컴파일러에 의해 만들어진다.
Create an .RC file for your project and #include in it the
WINDOWS.H file and the .RC file produced by the message compiler.

메세지 컴파일러로 만들어진 헤더 파일을 당신의 주 프로젝트 헤더파일
안에 포함하여 모든 모듈이 심볼릭 메세지 이름들을 접근하게 한다.
이제 그런 파일중 몇개를 좀더 상세히 보자 그래서 당신이 메세지컴파일
러로 만들수 있게 말이다. 우리는 메세지 셋의 전체를 보길 원치 않는다.
단지 하나나 두개정도만 어떻게 동작하는지 당신에게 보여줄것이다.
여기 내 메세지 소스파일, NTServMsg.mc의 처음부분이 있다.
Include the header file produced by the message compiler
in your main project header file so all modules have access
to the symbolic message names.
Let's look at some of these files in more detail so you can
see what you need to create and what the message compiler
creates for you. We won't look at the entire set of messages,
just one or two to show you how it works. Here's the first part
of my message source file, NTServMsg.mc:

MessageId=100
SymbolicName=EVMSG_INSTALLED
Language=English
The %1 service was installed.
.

MessageId=
SymbolicName=EVMSG_REMOVED
Language=English
The %1 service was removed.
.

MessageId=
SymbolicName=EVMSG_NOTREMOVED
Language=English
The %1 service could not be removed.
.

각 엔트리는 ID값을 갖았는데 만일 특정 값으로 정해주지 않았다면
그것은 이전의 메세지들에 의해 주어진 하나 이상의 값을 의미한다.
각 엔트리는 또한 심볼릭 이름이 있는데 이는 너의 코드에서 언어
인식자, 실제 메세지의 문장으로 쓰인다. 메세지들은 한문장 이상으로
확장가능한데 하나의 점을 갖은 하나의 라인으로 끝내면 된다.
Each entry has an ID value that, if not specifically set,
is simply one more than the value assigned to the message
before it. Each entry also has a symbolic name for use in
your code, a language identifier, and the text of the actual
message. Messages can span more than one line and are
terminated by a line containing a single period on its own.

메세지 컴파일러는 이진 파일을 생성하는데 이는 애플리케이션안에서
리소스로 사용된다. 두개의 파일을 당신의 소스 코드에 넣어야하는데
여기 나의 .RC파일이 있다.
The message compiler outputs a binary file to be used as a
resource in the application and two files for inclusion in
your source code. Here's my .RC file:

// NTServApp.rc
#include <windows.h>

// Include the message table resource script
// generated by the message compiler (MC).
#include "NTServMsg.rc"

여기 메세지 컴파일러에 의해 생성된 .RC파일이 있다.
Here's the .RC file the message compiler generated:

LANGUAGE 0x9,0x1
1 11 MSG00001.bin

보시듯이, 이기엔 많은 글이 있지는 않다.
As you can see, there's not a lot of text in these files!

마지막으로 메세지 컴파일러에 만들어진 파일인 헤더파일을
당신의 코드에 넣어야 한다. 여기에 샘플로 만들어진 것이
있다.
The last file generated by the message compiler is a header
file for you to include in your code. Here's just a part of
the one generated for the sample:

[..........]
//
// MessageId: EVMSG_INSTALLED
//
// MessageText:
//
//  The %1 service was installed.
//
#define EVMSG_INSTALLED                  0x00000064L

//
// MessageId: EVMSG_REMOVED
//
// MessageText:
//
//  The %1 service was removed.
//
#define EVMSG_REMOVED                    0x00000065L
[...........]

당신은 아마도 몇게의 나의 메세지들이 부아이템( %1같은거)이
있었다는걸 기억할 것이다. 어떻게 그런것들이 코드로 쓰였는지
우리가 실제로 메세지를 시스템 로그 파일에 적을때 봐라.
예를 들어서 설치 코드의 일부분을 봐라 이는 성공적으로 설치가
되었다고 이벤트 로그에 적힌 것이다. 이는 나의 CNTService::Is
Installed 함수의 일부분이다.
You might have noticed that several of my messages include
argument substitution items (%1 and so on). Let's see how
these are used in the code when we actually want to write
a message to one of the system's log files. For an example,
let's look at the part of the installation code that records
successful installation in the event log. This is part of my
CNTService::IsInstalled function:

[....]
LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);
[....]

LogEvent는 다른 CNTService 함수로 이벤트의 종류(정보,경고또는 에러)
와 이벤트 메세지의 ID와 세게의 인자가 부 문자열로 로그 메세지 형식
을 위해서 존재한다.
LogEvent is another CNTService function that uses the type
of event (information, warning, or error), the ID of the
event message, and up to three argument substitution strings
to form the log message:

// This function makes an entry into the application event log.
void CNTService::LogEvent(WORD wType, DWORD dwID,
                          const char* pszS1,
                          const char* pszS2,
                          const char* pszS3)
{
    const char* ps[3];
    ps[0] = pszS1;
    ps[1] = pszS2;
    ps[2] = pszS3;

    int iStr = 0;
    for (int i = 0; i < 3; i++) {
        if (ps[i] != NULL) iStr++;
    }

    // Check to see if the event source has been registered,
    // and if not then register it now.
    if (!m_hEventSource) {
        m_hEventSource = ::RegisterEventSource(NULL,  
             // local machine
                m_szServiceName); // source name
    }

    if (m_hEventSource) {
        ::ReportEvent(m_hEventSource,
                      wType,
                      0,
                      dwID,
                      NULL,   // sid
                      iStr,
                      0,
                      ps,
                      NULL);
    }
}

보시다싶이, 주된 작업은 ReportEvent라는 시스템 함수에
의해서 제어된다.
As you can see, the majority of the work is handled
by the ReportEvent system function.

그래서 이제 우리는 이벤트를 시스템 이벤트 로그에 기록하는것을
CNTService::LogEvent를 이용해서 할 수 있다. 이제 우리는 서비스를
위해서 코드를 만들것이다.
So now we have a way to record events in the system event log
by calling CNTService::LogEvent. Now we can move on to creating
some of the code for the service itself.

서비스코드 짜기
Writing the Service Code
윈32 SDK "훌터보기" 섹션에서 "시스템 서비스들"이라는 서브섹션에서
간단한 윈도우 NT 서비스를 만들기위해서 대부분 당신이 알아야 할
것에 대해서 나와 있다. 이 섹션에서 샘플 코드는 C로 되어 있는데
이는 꽤나 이해하기 쉽게 되어 있다. 나는 나의 CNTService클래스를
이 코드를 갖고 만들었다.
The Win32 SDK "Overviews" section has a subsection called
"System Services." This section contains most of what you
need to know in order to construct a simple Windows NT
service. The sample code in the section is in C and is
quite easy to follow. I based my CNTService class on the
material in this code.

서비스는 세가지 주요 함수를 갖고 있다.
A service contains three major functions:
메인 함수는 코드의 엔트리 포인트이다.
이는 우리가 언제 어떤 커맨드 라인 인자를 서비스가
설치되는지 삭제되는지 시작하는지 등등을 분석한다.
A main function that is the entry point of the code.
This is where we parse any command-line arguments and
get the service installed, removed, started, and so on.

이 함수는 실제 서비스 코드를 위한 엔트리 포인트를 제공한다.
샘플에서 이 함수(메인엔트리 포인트)는 ServiceMain을 호출
했다. 그러나 당신은 당신이 원하는 어떤 것대로든 호출할수
있다. 당신은 이 함수의 주소를 서비스 관리자에게 서비스가
시작할때 넘겨 주어도 된다.
A function that provides the entry point for the actual
service code. In the examples this function is called
ServiceMain, but you can call it anything you like.
You pass the address of this function to the service
manager when the service is first started.

함수는 커맨드 메세지를 서비스 관리자로 부터 얻어와서
처리한다. 예제에서 이 함수는 핸들러라고 불리는데 그러나
당신은 다른 어떤것으로 바꾸어도 된다.
A function that processes command messages from the service
manager. In the examples this function is called Handler,
but you can name it anything you like.

서비스 콜백 함수둘
Service Callback Functions
ServiceMain과 핸들러 함수가 시스템에 의해 호출되면 그것들은
반드시 파라미터 전달에 있어야 하고 시스템의 함수 호출 규약을
지켜야 한다. 이것은, 그것들은 C++클래스의 맴버함수가 될수
없다는 것을 의미한다. 이는 보기에도 불편하다. 우리는 윈NT
서비스를 하나의 C++클래스로 기능적으로 캡슐화 하기 원하므로
이 문제를 풀기위해서 나는 나의 ServiecMain과 Handler함수를
CNTService 클래스의 스태틱 맴버로 만들었다.
Since the ServiceMain and Handler functions are called from
the system, they must conform to the parameter-passing scheme
and calling convention of the operating system. This means
they can't simply be member functions of a C++ class. This
is slightly inconvenient, since we want to encapsulate the
functionality of a Windows NT service in a single C++ class.
To get around this problem, I created my ServiceMain and
Handler functions as static members of my CNTService class.
This enabled me to create functions that are callable by
the operating system. This doesn't provide a complete
solution, however, because the system does not allow
passing any form of user data to the called functions,
so we have no way to identify a call to either ServiceMain
or Handler with a specific instance of a C++ object. I use
a very simple but limiting solution to this problem. I
create a static variable that contains a pointer to the
C++ object. The variable is initialized when the object
is first created. This limits you to one C++ object per service
application. I did not consider this to be too restrictive.
Here's the declaration in the NTService.h file:

class CNTService
{
   [...]
   // static data
    static CNTService* m_pThis;   // nasty hack to get object ptr
  [...]
};

Here's how the m_pThis pointer gets initialized:

CNTService::CNTService(const char* szServiceName)
{
    // Copy the address of the current object so we can access it from
    // the static member callback functions.
    // WARNING: This limits the application to only one CNTService object.
    m_pThis = this;
   [...]
}

The CNTService Class
When I create C++ objects to encapsulate groups of
Microsoft WindowsR functions, I try to do more than
just make a member function for each Windows API I'm
encapsulating. I try to make the object easy to use,
and I try to help reduce the number of lines of code
needed to implement a particular section of a project.
So my object designs are based on "what do I want to do
with this object?" rather than "what does Windows do
with this set of APIs?".

The CNTService class contains member functions to parse a
command line, to handle installing and removing the service,
and to log events, and a set of virtual functions that you
can override in your derived class to handle requests from
the service control manager. We'll look at the use of most
of these functions as we go through the implementation of
the sample service.

If you want to create the simplest possible service, you
need only override CNTService::Run, which is where you
write the code to perform whatever task your service
provides. You also need to implement the main function.
If your service needs to perform some initialization,
such as reading data from the registry, it also needs
to override CNTService::OnInit. If you need to be able
to send command messages to your service, you can do
this by using the ControlService system function and
handling the requests in the service by overriding
CNTService::OnUserControl.

Using CNTService in the Sample Application
The NTService sample implements most of its functionality in the CMyService class, which is derived from CNTService. Here's the MyService.h header file:

// myservice.h

#include "ntservice.h"

class CMyService : public CNTService
{
public:
   CMyService();
   virtual BOOL OnInit();
      virtual void Run();
      virtual BOOL OnUserControl(DWORD dwOpcode);

      void SaveStatus();

   // Control parameters
   int m_iStartParam;
   int m_iIncParam;

   // Current state
   int m_iState;
};

As you can see, CMyService overrides OnInit, Run, and OnUserControl from CNTService. It also has a function called SaveStatus that is used to write data to the registry, and some member variables to hold the current state. The sample service increments an integer variable at regular intervals. The start value and increment value are both held as parameters in the registry. Not very exciting, but easy for you to follow. Let's move on now to see how the service is implemented.

Implementing the main Function
Having derived CMyService from CNTService, it's now a simple matter to implement the main function (in NTServApp.cpp):

int main(int argc, char* argv[])
{
    // Create the service object.
    CMyService MyService;
    
    // Parse for standard arguments (install, uninstall, version, etc).
    if (!MyService.ParseStandardArgs(argc, argv)) {

        // Didn't find any standard args so start the service.
        // Uncomment the DebugBreak line below to enter the debugger
        // when the service is started.
        //DebugBreak();
        MyService.StartService();
    }

    // When we get here, the service has been stopped.
    return MyService.m_Status.dwWin32ExitCode;
}

Not much code to look at here, but an awful lot happens when it's executed, so let's go through it step by step. First of all, we create an instance of the MyService class. The constructor sets the initial state and name of the service (MyService.cpp):

CMyService::CMyService()
:CNTService("NT Service Demonstration")
{
   m_iStartParam = 0;
   m_iIncParam = 1;
   m_iState = m_iStartParam;
}

A call is then made to ParseStandardArgs to see if the command line contains a request to install the service (-i), remove it (-u), or report its version number (-v). CNTService::ParseStandardArgs calls CNTService::IsInstalled, CNTService::Install, and CNTService::Uninstall to process these requests. If no recognizable command-line arguments are found, it is assumed that the service control manager is trying to start the service and a call is made to StartService. This function does not return until the service stops running. The call to DebugBreak causes a break into the debugger when the service is first started. When you are done debugging the code, you can comment out or delete this line.

Installing and Removing the Service
Installing the service is handled by CNTService::Install, which registers the service with the Windows NT service manager and makes entries in the registry to support logging messages when the service is running.

Removing the service is handled by CNTService::Uninstall, which simply informs the service manager that the service is no longer required. CNTService::Uninstall does not remove the actual service executable file.

Writing Your Service Code
Now we need to write the code that actually implements your service. For the NTService sample there are three major functions to write. These cover initialization, actually running the service, and responding to control requests.

Initialization
The registry has a location for services to store their parameters: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services. This is where I chose to store the configuration data for my service. I created a Parameters key and under that stored the values I wanted to save. So when the service starts, the OnInit function is called; it reads the initial settings from this place in the registry.

BOOL CMyService::OnInit()
{
  // Read the registry parameters.
    // Try opening the registry key:
    // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<AppName>\Parameters
    HKEY hkey;
  char szKey[1024];
  strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\");
  strcat(szKey, m_szServiceName);
  strcat(szKey, "\\Parameters");
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                     szKey,
                     0,
                     KEY_QUERY_VALUE,
                     &hkey) == ERROR_SUCCESS) {
        // Yes we are installed.
        DWORD dwType = 0;
        DWORD dwSize = sizeof(m_iStartParam);
        RegQueryValueEx(hkey,
                        "Start",
                        NULL,
                        &dwType,
                        (BYTE*)&m_iStartParam,
                        &dwSize);
        dwSize = sizeof(m_iIncParam);
        RegQueryValueEx(hkey,
                        "Inc",
                        NULL,
                        &dwType,
                        (BYTE*)&m_iIncParam,
                        &dwSize);
        RegCloseKey(hkey);
    }

  // Set the initial state.
  m_iState = m_iStartParam;

  return TRUE;
}

Now that we have the service parameters, we are ready to run the service.

Running the Service
The main body of the service code is executed when the Run function is called. My sample is rather simple:

void CMyService::Run()
{
    while (m_bIsRunning) {

    // Sleep for a while.
        DebugMsg("My service is sleeping (%lu)...", m_iState);
        Sleep(1000);

    // Update the current state.
    m_iState += m_iIncParam;
    }
}

Note that this function does not exit until the service is stopped. The CNTService::m_bIsRunning flag is set to FALSE when a request is made to stop the service. You can also override OnStop and/or OnShutdown if you need to perform cleanup when your service terminates.

Responding to Control Requests
You can communicate with your service in whatever way suits you?named pipes, thought transference, sticky notes, and so on?but for simple requests the system function ControlService is very easy to use. CNTService provides a handler for non-standard (that is, user) messages sent through the ControlService function. My sample uses a single message to save the current state of the service in the registry so that other applications can inspect it. I'm not proposing this as the best way to monitor a service, just one that was easy to implement and interesting to code. User messages sent by means of ControlService must be in the range 128 to 255. I defined a constant, SERVICE_CONTROL_USER, as the base value (128). Messages in the user range are sent to CNTService:: OnUserControl and are handled in the sample service this way:

BOOL CMyService::OnUserControl(DWORD dwOpcode)
{
    switch (dwOpcode) {
    case SERVICE_CONTROL_USER + 0:

        // Save the current status in the registry.
        SaveStatus();
        return TRUE;

    default:
        break;
    }
    return FALSE;   // say not handled
}

SaveStatus is a local function that saves the service state in the registry.

Debugging a Windows NT Service
The main function contains a call to DebugBreak, which causes the system debugger to be activated when the service is first started. You can monitor the debug messages from the service in the debugger's command window. You can use CNTService::DebugMsg from your service to report events of interest during debugging.

You'll need to install the system debugger (WinDbg) from the Win32 SDK in order to debug your service code.

One important point to note is that you really can't stop the service and single-step it when it's being controlled by the service manager, because the service manager will time out requests to the service and terminate the service thread. So you can really only get your service to spit out messages to track its progress and watch them in the debugger window.

When the service is started (for example, from the Services applet in Control Panel), the debugger will start with the service thread halted. You need to let the thread run by clicking the GO button or pressing the F5 key. Then you can observe the service's progress in the debugger window.

The following text shows an example of starting and stopping the service:

Module Load: WinDebug/NTService.exe  (symbol loading deferred)
Thread Create:  Process=0, Thread=0
Module Load: C:\NT351\system32\NTDLL.DLL  (symbol loading deferred)
Module Load: C:\NT351\system32\KERNEL32.DLL  (symbol loading deferred)
Module Load: C:\NT351\system32\ADVAPI32.DLL  (symbol loading deferred)
Module Load: C:\NT351\system32\RPCRT4.DLL  (symbol loading deferred)
Thread Create:  Process=0, Thread=1
*** WARNING: symbols checksum is wrong 0x0005830f 0x0005224f for C:\NT351\symbols\dll\NTDLL.DBG
Module Load: C:\NT351\symbols\dll\NTDLL.DBG  (symbols loaded)
Thread Terminate:  Process=0, Thread=1, Exit Code=0
Hard coded breakpoint hit
Hard coded breakpoint hit
[](130): CNTService::CNTService()
Module Load: C:\NT351\SYSTEM32\RPCLTC1.DLL  (symbol loading deferred)
[NT Service Demonstration](130): Calling StartServiceCtrlDispatcher()
Thread Create:  Process=0, Thread=2
[NT Service Demonstration](174): Entering CNTService::ServiceMain()
[NT Service Demonstration](174): Entering CNTService::Initialize()
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 2)
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 4)
[NT Service Demonstration](174): Entering CNTService::Run()
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](174): Sleeping...
[NT Service Demonstration](130): CNTService::Handler(1)
[NT Service Demonstration](130): Entering CNTService::Stop()
[NT Service Demonstration](130): CNTService::SetStatus(3026680, 3)
[NT Service Demonstration](130): Leaving CNTService::Stop()
[NT Service Demonstration](130): Updating status (3026680, 3)
[NT Service Demonstration](174): Leaving CNTService::Run()
[NT Service Demonstration](174): Leaving CNTService::Initialize()
[NT Service Demonstration](174): Leaving CNTService::ServiceMain()
[NT Service Demonstration](174): CNTService::SetStatus(3026680, 1)
Thread Terminate:  Process=0, Thread=2, Exit Code=0
[NT Service Demonstration](130): Returned from StartServiceCtrlDispatcher()
Module Unload: WinDebug/NTService.exe
Module Unload: C:\NT351\system32\NTDLL.DLL
Module Unload: C:\NT351\system32\KERNEL32.DLL
Module Unload: C:\NT351\system32\ADVAPI32.DLL
Module Unload: C:\NT351\system32\RPCRT4.DLL
Module Unload: C:\NT351\SYSTEM32\RPCLTC1.DLL
Thread Terminate:  Process=0, Thread=0, Exit Code=0
Process Terminate:  Process=0, Exit Code=0
>

Summary
Perhaps C++ isn't ideal for creating a Windows NT service, but having a single class from which you can derive your own service certainly makes it easy to get your own service started. As usual, if you or your relatives, friends, pets, or acquaintances have comments or suggestions, please mail them to me: nigelt@microsoft.com.

+ Recent posts