Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

윈도우 11에서 윈도우 서비스 개발하기: 베스트 프랙티스 및 도구


Recommended Posts

Muminjon의 "Developing Windows Services in Windows 11: Best Practices and Tools"를 번역했습니다. (원문 작성: 2023년 4월, 최종 번역: 2023년 12월)

윈도우 소프트웨어를 개발하다 보면, 애플리케이션이 24시간 실행되도록, 즉 컴퓨터가 실행되는 계속 작동하도록 개발해야 하는 경우가 있다. 대체로, 네트워크 서버들이 그렇다. 또는 데스크탑 컴퓨터에서 작동하는 모니터링 애플리케이션들이 해당된다. 이런 경우 상호 작용이 최소화되거나 아예 없는 콘솔 애플리케이션을 만드는 것을 생각해볼 수 있지만 이보다 더 나은 해법이 있다.

단순 콘솔 또는 최소화된 일반 GUI 앱은 문제에 직면하게 될 수 있다. 예를 들면, 윈도우 세션 종료, 재부팅, 사용자 권한 등이다. 이런 문제를 해결하는 방법은 윈도우 서비스를 개발하는 것이다. 이 튜토리얼에서는 델파이를 사용하여 윈도우 서비스를 구축하는 방법을 살펴본다. 이 글의 안내는 윈도우 서비스를 개발하기 위한 일종의 템플릿으로 사용할 수 있다.

목차


윈도우 서비스를 만들어야 하는 이유는?

오랫동안 실행되어야 하는 서비스를 만드는 데에는 많은 이유가 있다. 예를 들면,

  • CPU-집약적인 데이터를 처리하기.
  • 작업 항목을 백그라운드에서 대기열에 추가하기.
  • 일정에 따라 시간-기반 작업을 수행하기
  • 눈에 띄지 않는 작업 ('헬퍼' 또는 유틸리티 앱의 기능들) 수행하기

백그라운드 서비스 처리는 대체로 사용자 인터페이스(UI)가 들어가지 않는다. 하지만, 이를 중심으로 그 주변에 UI를 구축하는 경우도 있다.

윈도우 서비스를 개발하기 위한 전제 조건은?

서비스 앱 개발에 대해 알아보기 전에,델파이 IDE의 최신 버전 살펴보기 바란다. 개선 사항과 새 기능들을 활용하면, 개발 절차가 훨씬 더 원활하다. 게다가, 델파이 커뮤니티 에디션을 무료로 사용하여 델파이 프로그래밍 언어와 해당 구문에 익숙해질 수 있다.

위도우즈 서비스 애플리케이션은 어떻게 작동하는가?

서비스 애플리케이션은 클라이언트 애플리케이션의 요청을 받고, 그 요청을 처리하고, 정보를 그 클라이언트 애플리케이션에게 반환한다. 웹, FTP, 이메일 서버가 서비스 애플리케이션의 대표적인 예다.  

윈도우 서비스 애플리케이션이란 윈도우 애플리케이션 중에서 사용자가 로그인하지 않아도 실행될 수 있는 것을 의미한다. 윈도우 서비스 애플리케이션이 데스크탑과 상호작용하는 경우는 거의 없다. 이 튜토리얼에서, 우리는 델파이를 사용하여 윈도우 서비스 애플리케이션을 만들어본다.

우리는 서비스 애플리케이션이 실행될 때, 기본으로 사용할 사용자 권한을 지정할 수 있다. 그 사용자는 가상인 '서비스 사용자'이거나 또는 (시스템 액세스 권한이 있는) 실제 일반 사용자일 수 있다. 이 사용자 권한은 폴더에 액세스 할 수 있는 권한이 서비스 앱에게 주어지는지 아닌지에 대해 영향을 준다는 점을 아는 것이 중요하다.  즉, 권한이 필요한 폴더 그리고 '맵핑된' 네트워크 폴더(누군가가 네트워크 폴더를 가리키는 Z: 드라이브와 같은 맵핑을 생성한 경우)를 사용하지 못하는 이유가 서비스 앱의 사용자 권한인 경우가 많다. 경로 지정은 가능하면 항상 전체 UNC 경로 이름을 사용해야 한다. 윈도우 시스템 호출 또는 델파이의 런타임 TPath 타입 함수를 사용하여 특수한 폴더의 올바른 위치를 얻을 수 있다. 예를 들어, %APPDATA% 폴더 위치 그리고 "My Documents" 가상 경로 등을 사용하여 올바른 위치를 얻어야 한다.

윈도우 서비스 애플리케이션은 많은 기능을 제공할 수 있다. 서비스 유틸리티 도구를 시작해서 그 목록을 살펴 보자. 이 서비스들 중에는 여러분이 사용하는 주요 GUI 애플리케이션의 핵심 서비스들도 들어 있다. 

services-windows-11-2230066.png?ssl=1

 

언제 윈도우 서비스를 작성해야 하는가?

예전에 나는 파일 서버의 여유 디스크 공간을 모니터링하는 시스템 모니터링 유틸리티가 필요한 적이 있었다. 그래서, 1 분마다 디스크 공간을 확인하고 그 정보를 로그 파일에 기록하는 유틸리티를 작성했다. 그런데, 그 유틸리티는 사용자가 로그인을 해야 작동하는 것이었고, 사용자가 로그아웃하면 그 앱 역시 종료되었다. 해결책은 앱을 윈도우 서비스로 다시 만들어서, 컴퓨터의 전원이 켜져있는 한 계속 실행되도록 하는 것이었다.

델파이, C++빌더, RAD 스튜디오는 비록 전형적으로 사용자가 마주하는 대화형 애플리케이션을 만드는 것에 더 최적화되어 있지만, 서비스 애플리케이션을 쉽게 생성할 수 있는 능력 그 이상을 가지고 있다.

윈도우 서비스 프로젝트를 델파이(Delphi)로 만드는 방법은?

델파이에서 새 프로젝트를 생성하려면, 델파이 개발 환경이 컴퓨터에 있어야 한다. 일단 델파이를 시작하고 실행하게 되면, 새 프로젝트를 생성하여 시작할 수 있다.

  1. 델파이를 이용하여, 새 윈도우 서비스 프로젝트를 RAD 스튜디오에서 생성하려면, 다음 단계를 따르면 된다.
  2. File 메뉴를 클릭하고 New->Other를 선택한다. 그러면 New Items(새 항목) 대화 상자가 나타난다. 대화 상자 왼쪽에서, Delphi를 선택한 다음 Windows 카테고리에서 Windows Service 프로젝트 유형을 선택한다.
  3. OK를 클릭하면 새 프로젝트가 생성된다.

windows-service-RAD-Studio-4782815.png?s

참고로, C++ 빌더를 사용할 때에도, 이 절차는 비슷하다.

새 프로젝트가 생성되고 나면, 프로젝트의 속성과 옵션을 설정해야 한다. "Project Manager"창에서 프로젝트 이름을 마우스 오른쪽 버튼으로 클릭하고 "Options"를 선택한다. 그러면 "Project Options" 대화 상자가 나타난다. 이 대화 상자에서는 대상 플랫폼, 출력 디렉터리, 컴파일러 옵션 등 프로젝트에 대한 다양한 옵션을 설정할 수 있다. 서비스 개발을 진행하기 전에 프로젝트 요구 사항에 맞게 이 옵션들을 설정해야 한다.

윈도우 서비스 앱을 델파이를 사용하여 구현하는 방법은?

델파이에서 새 Windows service 프로젝트를 생성하고, 그 프로젝트의 속성과 옵션을 설정했다면, 그 다음 단계는 해당 서비스 자체를 구현하는 것이다. 구현은 주로 메인 서비스 유닛에 코드를 추가하는 작업이다. 기본 설정 대로라면, 메인 서비스 유닛은 "Unit1.pas"라는 파일이다.

시작하려면, "Project Manager"창에서 "Unit1.pas" 파일을 두 번 클릭한다. 그러면, 파일이 코드 에디터에 열린다. 그 파일 안엔느 이미 델파이 서비스 애플리케이션의 코드 골격이 들어 있다.

이 유닛에 자신만의 코드를 추가하려면, 이 윈도우 서비스의 동작을 정의해야 한다. 다양한 서비스 이벤트를 다루는 방식으로 하면 된다. 이벤트 중에는 서비스 시작, 중지, 일시 정지 이벤트도 들어 있다. 이 이벤트들은 말 그대로 서비스가 시작, 중지, 일시 정지될 때 실행될 때 작동한다.

이 이벤트들을 처리하려면, 델파이에 내장된 서비스 컴포넌트인 “TService” 클래스를 사용하면 된다. 이 컴포넌트는 해당 서비스의 동작을 제어하는 데 사용되는 다양한 메소드와 프로퍼티를 제공한다. “Start” 메서드와 “Stop” 메소드도 여기에 해당된다. 

예를 들어, "OnStart” 이벤트 핸들러를 사용하면, 서비스가 시작될 때 실행되어야 하는 코드를 정의할 수 있다. 그렇게 하려면, 다음 코드를 "Unit1.pas" 파일에 추가하면 된다.

procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
  // 여기에 코드를 적으면, 서비스가 시작될 때 처리된다
  // 예를 들어, 기록할 로그 항목을 만들기 등
end;

이와 비슷하게, "OnStop" 이벤트 핸들러를 사용하면, 서비스가 중지될 때 실행되어야 하는 코드를 정의할 수 있다. 그렇게 하려면, 다음 코드를 "Unit1.pas" 파일에 추가하면 된다.

procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  // 여기에 코드를 적으면, 서비스가 중지될 때 처리된다
  // 지속하던 연결을 종료하기 또는 로그를 쓰기 등
end;

시작 이벤트나 중지 이벤트 외에도, 서비스가 일시 정지될 때 작동하는 pause 이벤트를 처리해야 할 수도 있다. 그렇게 하려면, “OnStart” 또는 “OnStop” 이벤트 핸들러와 비슷하게, “OnPause”이벤트 핸들러를 사용하면 된다.

예를 들어, 다음 코드를 "Unit1.pas" 파일에 추가하면, 일시 정지 이벤트를 처리할 수 있다.

procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
  // 여기에 코드를 적으면, 사용자가 서비스 메뉴에서
   // 이 서비스에 대해 "일시 정지(pause)"를 선택하거나,
   // 이 서비스를 중지하는 명령을 실행했을 때 처리된다.
  // 서비스가 정지했을 때와 같은 것이 아님을 명심하라!
end;

이처럼 각 이벤트 핸들러 안에 여러분의 코드를 추가하여, 해당 이벤트가 발동하면 서비스가 어떤 행위를 할 것인지를 정의하면 된다. 예를 들면, “TService" 구성 요소의 “Start” 및 “Stop” 메소드를 사용하여 타이머나 스레드를 시작하거나 중지할 수 있으며, “Pause” 메소드를 사용하여 작업 실행을 일시 중지할 수도 있다.

메인 서비스 유닛 안에 코드를 추가했고, 서비스의 이벤트들을 처리하도록 코드를 넣었다면, 이제 이 서비스를 디버깅하고 테스트하면 된다. 그러려면 델파이 디버거와 윈도우 서비스 관리자를 사용하면 된다.

윈도우 서비스 애플리케이션 기능을 구현하는 방법은?

Vcl.SvcMgr 유닛을 유닛의 Uses 절 안에 추가한다.

메인 폼의 조상을 TService로 바꾼다. 그렇게 하려면, TForm 클래스의 선언을 다음과 같이 수정하면 된다.

type
  TService1 = class(TService)

소스 코드 전체는 다음과 같다.

unit ServiceUnit;
 
interface
 
uses
  Winapi.Windows
, Winapi.Messages
, System.SysUtils
, System.Classes
, Vcl.Graphics
, Vcl.Controls
, Vcl.SvcMgr
, Vcl.Dialogs
BackgroundThreadUnit
, System.Win.Registry;
 
type
  TService1 = class(TService)
    procedure ServiceExecute(Sender: TService);
    procedure ServiceStart(Sender: TService; var Started: Boolean);
    procedure ServiceStop(Sender: TService; var Stopped: Boolean);
    procedure ServicePause(Sender: TService; var Paused: Boolean);
    procedure ServiceContinue(Sender: TService; var Continued: Boolean);
    procedure ServiceAfterInstall(Sender: TService);
  private
    FBackgroundThread: TBackgroundThread;
    { Private declarations }
  public
    function GetServiceController: TServiceController; override;
    { Public declarations }
  end;
 
{$R *.dfm}
 
var
  MyService: TService1;
 
implementation
 
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
  MyService.Controller(CtrlCode);
end;
 
procedure TService1.ServiceExecute(Sender: TService);
begin
  while not Terminated do
  begin
    ServiceThread.ProcessRequests(false);
    TThread.Sleep(1000);
  end;
end;
 
function TService1.GetServiceController: TServiceController;
begin
  Result := ServiceController;
end;
 
procedure TService1.ServiceContinue(Sender: TService;
  var Continued: Boolean);
begin
  FBackgroundThread.Continue;
  Continued := True;
end;
 
procedure TService1.ServiceAfterInstall(Sender: TService);
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create(KEY_READ or KEY_WRITE);
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    if Reg.OpenKey('SYSTEMCurrentControlSetServices' + name, false) then
    begin
      Reg.WriteString('Description', 'Blogs.Embarcadero.com');
      Reg.CloseKey;
    end;
  finally
    Reg.Free;
  end;
end;
 
procedure TService1.ServicePause(Sender: TService; var Paused: Boolean);
begin
  FBackgroundThread.Pause;
  Paused := True;
end;
 
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  FBackgroundThread.Terminate;
  FBackgroundThread.WaitFor;
  FreeAndNil(FBackgroundThread);
  Stopped := True;
end;
 
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
  FBackgroundThread := TBackgroundThread.Create(True);
  FBackgroundThread.Start;
  Started := True;
end;
 
end.

BackgroundThreadUnit 유닛은 다음과 같다.

unit BackgroundThreadUnit;
 
interface
 
uses
  System.Classes;
 
type
  TBackgroundThread = class(TThread)
  private
    FPaused: Boolean;
    // FTerminated: Boolean;
    // FOnTerminate: TNotifyEvent;
  protected
    procedure Execute; override;
  public
    procedure Pause;
    procedure Continue;
    // procedure Terminate;
    // property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
  end;
 
implementation
 
uses
  System.SysUtils, System.IOUtils;
 
procedure TBackgroundThread.Continue;
begin
  FPaused := False;
end;
 
  // process something here
procedure TBackgroundThread.Execute;
var
  LogFile: TextFile;
begin
  try
    FPaused := False;
    AssignFile(LogFile, 'C:TempLogs.log');
    Rewrite(LogFile);
 
    while not Terminated do
    begin
      if not FPaused then
      begin
        WriteLn(LogFile, 'Logs From Background Thread: ' + DateTimeToStr(Now));
      end;
      TThread.Sleep(1000);
    end;
  finally
    CloseFile(LogFile);
  end;
end;
 
procedure TBackgroundThread.Pause;
begin
  FPaused := True;
end;
 
end.

이제 이 서비스를 빌드하면 된다. Projects 창에서, 컨텍스트 메뉴를 열고, 이 서비스를 빌드한다. 다음과 같이 하면 된다.

build-the-service-4653368.png?ssl=1

윈도우 서비스를 델파이에서 빌드하기

 

윈도우 서비스 관리자를 이용해 서비스 테스트하기

윈도우 서비스 관리자를 사용하여, 이 서비스를 테스트할 수 있다. 윈도우 서비스 관리자 유틸리티를 사용하면 서비스를 시작, 중지, 일시중지 및 다시 시작할 수 있다. 또한 해당 상태와 발생하는 오류를 볼 수 있다.

이 서비스를 설치하려면, 다음 단계를 따르면 된다.

  • 해당 폴더로 이동하여 터미널을 연다.(실행파일은 관리자 권한으로 실행되도록 구성해야 한다.)
  • 서비스 이름을 타이핑하고 뒤에 “/install”을 추가하여 명령을 실행한다.

install-windows-11-service-built-with-De

해당 폴더로 이동한 후, 터미널에서 연다.

windows-service-installed-successfully-i

터미널을 사용하여 윈도우 서비스를 설치한다

일단 서비스가 설치되면, 윈도우 서비스 관리자를 사용하여 서비스를 시작, 중지, 일시 중지 및 재개할 수 있다. 그렇게 하려면, 윈도우 제어판(Control Panel)의 관리 도구 폴더에서 서비스(Services) 앱을 연다. 서비스 목록에서 서비스를 찾아, 마우스 오른쪽 버튼으로 클릭하고 컨텍스트 메뉴에서 원하는 작업을 선택한다.

windows-services-Delphi-8866433.png?ssl=

위 화면과 같이 제어판에서 해당 서비스를 시작, 중지, 일시 정지 할 수 있다.

몇 초 후에, 서비스 관리자에서 서비스를 중지하고 로그 파일을 확인한다.

log-file-delphi-windows-11-service-demo-

Logs.log 파일

이 튜토리얼에서 사용한 전체 프로젝트 소스 코드는 이 리포지토리에서 얻을 수 있다. 또한, 다른 튜토리얼을 찾아보는 것도 잊지 말자. 멀티 플랫폼 네이티브 애플리케이션을 만드는 방법 등을 배울 수도 있다.

자주하는 질문(FAQ)

윈도우 서비스를 디버깅하는 방법은?

윈도우 서비스를 삭제하는 방법은?

PowerShell을 관리자 권한으로 열고, 명령어 sc delete ServiceName를 입력한다.  

윈도우 서비스 앱을 자동으로 설치하려면?

메시지 상자가 나타나는 것을 방지하려면, 다음과 같이 명령줄 끝에 “/silent”를 추가하면 된다.
myserviceapp.exe /install /silent

64비트 윈도우에서 32비트 서비스를 실행할 수 있는가?

실행할 수 있다. 32비트 서비스는 64비트 버전의 윈도우에서 정상적으로 실행될 수 있다. 그러나, 네트워크 시스템 관리자들 중에는 64비트 서비스 이외의 서비스가 실행하지 못하도록 하는 윈도우 그룹 정책 옵션을 설정해 놓기도 한다는 점을 참고하자. 대부분은 이렇게 하지 않는다. 하지만 그럴 수도 있다는 점을 알아두자.

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.