Windows95/NTデーモンプロセスの作り方がわかりたい

戻る

○ウィンドウを表示するだけがWindowsプログラムではない
Windowsのアプリケーションは、一般的にウインドウを表示させてユーザの入力 に応じて処理を行うことが基本になっています。
しかし、バックグラウンドでユーザが介在しなくても動作できる処理も多くあります。 このようなアプリケーションを、Windows上で実現するための手段について TELNETクライアントで接続できるエコーサーバを例に考えてみましょう。

8-1 デーモンプロセスとは

○UNIXのデーモンプロセス
FTP や TELENET クライアントアプリケーションを使うことによってネットワーク 上のUNIXXマシン(サーバ)に接続し、そのマシンのリソースを使用することが できます。サーバでは、クライアントに提供する機能を、デーモンプロセス (Daemon Process)として動作させています。
 デーモンプロセスはシステムに管理され、ネットワークやコンソールからログイン するユーザは、そのプロセスがどのように管理されているのかを意識せずに、サーバが 提供する機能を利用することができます。
デーモンプロセスは一般的に、次のような特徴があります。
  1. デーモンプロセスはシステムが管理する
  2. システムが初期化されるときに一度だけ起動される
  3. システムの稼動時に起動されシステムの終了時に終了する
  4. 実行中、ほとんどの時間をイベント待ちのままで過ごす
  5. サービスの要求を実行するためにプロセス(スレッド)を生成する
つまり、ユーザが介在せずバックグラウンドでイベントに対する処理を行うことが デーモンプロセスの使命となります。
このようなアプリケーションは、通常のアプリケーションと変わりありませんが、 唯一異なる点はプロセスがユーザの「持ち物」でなく、システムの「持ち物」で、 入出力用のコンソールを持たないことです。
UNIXでは、ダム端末(TELNETなど)から複数のユーザが1台のマシンにログイン して使用するのが一般的です。
ログインしたユーザが起動したプロセスは、ログアウトすることによって終了します。 このため、あるユーザに特化したプロセスではそのユーザが ログインしているか どうかに左右されるため問題があります。
そこで、起動時にプロセスをバックグラウンドで実行し、プロセスグループを独立 させます(リスト8-1)。

○MS-DOSのTSRプログラム
パソコンの世界では長い間、MS-DOSが標準のOSとして使われてきました。
MS-DOSはシングルタスクのOSであるため、バックグラウンドで動作させる デーモンプロセスのような処理は、ハードウェアやシステムに大きく依存した処理 (たとえば、98でタイマーが必要なときは VSYNC 割り込み)を使った、TSR (Terminate and Stay Resident)プログラムで行ないます。
TSRプログラムは、アプリケーションとデバイスドライバの区別がはっきりしない ものが多く、処理中に INT 21H ファンクションコールなどの、OSが提供する システムコールを使えない場合がほとんどです。このため、TSRプログラムはデーモン プロセスと同じような用途で用いられることもありますが、デバイスドライバ方が 近い存在になることが多いでしょう。


リスト8-1:UNIXのデーモンプロセスの例


void main(void) { if(fork() != 0){ exit(0); ← 親プロセスは終了させる } /* ここから子プロセス(バックグラウンドプロセス) */ setpgrp(); ← プロセスグループを切り離す* (SystemV系UNIX) /* デーモンの処理 */ while(){ : : } } * システムによって関数名が異なることがあります。



8-2 Windowsソケットインターフェース

○WindowsのソケットインターフェースWinSock
UNIXでは通常、通信にTCP/IPを使用しソケットインターフェスが使われます。 Windowsでも、LANやPPP接続で TCP/IPプロトコルによってインターネットに 接続されている方も多いと思います。
ソケットインターフェースは、カリフォルニア大学バークレイ校で開発され、 1982年にBSD版UNIX(4.1cBSD) に提供されました。
Windows では、1993年に 4.3BSD UNIX のソケットを基本にWindows Socket Version 1.1 が発表され、Windows95/NTでは標準で用意され、TCP/IP プロトコルをインストール することで利用可能になります。 Windows Socket (WinSock) は、UNIXで提供されている API と Windows に特化した API(WSAxxxxxx)を持っています。  WinSock API は、DLLで提供され、ソケットインターフェースを利用するには、 必ず WSAStartup()WSACleanup() を使用して、WinSockの初期化と 解放を行う必要があります。

○サーバーとの接続
TELNET や FTP などは、UNIXの標準的なプロトコルで、これらのサービスを Windows95/NT から利用するには TCP/IP と WinSock は必ず必要になります。
クライアントがサーバに接続するときは、サーバマシンを特定するIPアドレスと ポート番号が必要です。ポート番号はシステムで予約されているもの (Well known address 0〜1023)と、ユーザが任意に定義できる番号(1024〜)が あります(図8-1)。

○TELNETについて
TELNETは、ネットワーク上のサーバにリモート端末に接続するために使用される プロトコルです。パソコンのコンソールは本体に直接接続されるディスプレイ ですので、あまり馴染みがないと思いますが、言い換えれば、Windows の MS-DOS プロンプト(VM)が、ネットワーク上の他のマシンで処理を行っているといったイメージ になります。
Windows95/NT に TCP/IPプロトコルをインストールすると、TELNETクライアント (TELNET.EXE)も自動的にインストールされます。TELNET.EXE は クライアントですので、利用するには、TELENTサーバがネットワーク上に存在 しなければなりません。




図8-1:ポート番号の例
サーバ (ip : xxx.xxx.xxx.xxx) +------------+ コンソール ftp -----------------→□21番ポート | +----+ | | | | telnet --------------→□23番ポート | +-+----+-+ | | +--------+ lpr -----------------→□515番ポート| Windows95/NT +------------+ UNIX
図8-2:TELNETの構成
サーバ +------------+ コンソール | [プロセス] | +----+ | [プロセス] | | | | | +-+----+-+ | [シェル] | +--------+ +------------+ ↑↓ ↑↓ [TELNETクライアント] [TELNETサーバ(デーモン)] | | [TCP] [TCP] | | [IP・データリンク層] [IP・データリンク層] +--------------------[ネットワーク]------------------+ ↑| ↑| |+-- クライアントからのデータ(文字列゙+[CR]+[LF]) ---+| +-- サーバからのデータ(文字列+[CR]+[LF]+[IAC]+[GA]) --+ (デフォルトNVT(ネットワーク仮想端末)モードでの通信シーケンス)



8-3 WindowsNTのサービスプロセス

○デーモンと同様の役割を果たすWindowsNTのサービス
WindowsNTでは、UNIXのデーモンと同様の役割を果たす「サービス(Service)」を 提供しています。サービスは、コントロールパネルアプリケーションの「サービス」の 設定ウインドウで設定を行うことができます。サービスは、システムのブート時に起動 することも、ユーザが起動、終了を制御することもできます。
Win32サービスは、32ビットコンソールアプリケーションとして実装し、メイン関数 は WinMain() でなく、main() 関数になります。
サービスには「Win32サービス」と「ドライバサービス」があり、WindowsNTシステム では、UNIXのデーモンプロセスが行うような処理は「Win32サービス」が 受け持っています。

○サービス制御マネージャ
Win32サービスは、コントロールパネルの「サービス」で設定を行います。 Win32サービスは、サービス制御マネージャに接続するための関数や、状態を通知する 関数を呼び出すことによって、Win32サービスとして統一した動作をします。
サービス制御マネージャは、システム上のサービスを集中管理し、次のような機能 を提供します。
  1. サービスの起動・終了を行う
  2. サービスの状態を管理する
  3. サービスへの制御要求の受け渡し
  4. サービスに関するレジストリ項目の管理を行う
Win32サービスは、WindowsNT 3.5リソースキットまたはWindowsNT 3.5 Server 付属 のサーバマネージャなどを使用して、リモートマシンから起動/終了/設定変更を 行うことができます。

○WindowsNT3.51の新しいAPI
WindowsNT 3.51では、Windows95で追加されたインタフェースの追加以外にも、 新しいAPIが多く追加されています。ソケットインタフェースを使用した通信サーバ を設計しようとしたとき、ドメインやローカルマシンにログオンしなければ セキュリティの対象になりません。このとき、サービスに接続したクライアントの処理 は、サービスは起動したユーザのアクセス権(通常 SYSTEM ユーザ)で全ての処理を 行うことになります。
WindowsNT 3.5以前のバージョンでは、直接セキュリティの情報からユーザ名や パスワードを検索してアクセス権の認証を行う必要がありましたが、WindowsNT 3.51 では、Win32 API LogonUser() を使用してアクセス権のチェックを行い、ログオンした ユーザのアクセストークンを得ることができ、 Win32 API CreateProcessAsUser() を使用して、そのアクセストークンでプロセスを起動できるようになりました。
 その他、多くのAPIが追加されていますので、WindowsNTのアプリケーションを これから開発されるときは、最新の Win32 SDK を参照することを お勧めします(注1)。
(注1)最新のWin32 SDKで追加されているAPIを知るには、Visual C++ 4.x (\MSDEV\LIB) と最新のWin32 SDK (\MSTOOLS\LIB) に ある WIN32API.CSV ファイルの差分を取れば解ります。 WIN32API.CSV は、Microsoft Exel などの表計算ソフトで利用 できる形式ですが、テキスト形式ですのでエディタでも参照することができます。

8-4 WindowsNT Win32サービスのデバッグ法

通常プログラムをデバッグするときは、デバッガからプログラムを起動させてから、 ブレークポイントの設定や、トレースを行います。 しかし、Win32サービスは、コントロールパネルの「サービス」アイコンのサービス マネージャから起動されなければなりません。したがって、デバッガからサービス を起動させることはできません。
このようなプロセスをデバッグするために、すでに起動しているプロセスを横取り して、デバッガの管理下に置く方法を提供しています。
ここでは、Win32サービスをデバッグするときの手順について説明していますが、 普通のアプリケーションでも同じように行えますし、Windows95 でも同様に可能です。 Windows95のプロセスビューア(PVIEW95.EXE) は、Windows95 に対応した Win32 SDKで提供されます。

☆方法
  1. デバッグを行うWin32サービスを起動する
  2. Win32 SDK または Visual C++ 2.0 のプロセスビューア(PVIEWER.EXE)を 起動する
  3. デバッグしたいEXEファイルのプロセスIDを調べる
  4. WinDebug を起動する
  5. FileメニューのOpenからソースファイルを開く
  6. ブレークポイントの設定を行う
  7. WinDebugのコマンドウインドウから .attach xx(xx は16進のプロセスID)を入力する
  8. [F5]キーでデバッグを開始する

8-5 Windows95の非表示ウインドウプロセス

Windows95(Win32c API)には「サービス」がありません。 したがって、サービス以外の方法でバックグラウンド処理を行わせる必要が あります。
Windows95でバックグラウンド処理を行うには、以下の方法が考えられます。
  1. DOSのTSRで行う (Windows95ではVxDの方がよい)
  2. VxDで行う (WindowsNTのドライバサービスに相当する)
  3. アイコン化したウインドウで処理を行う
  4. ウインドウを表示せず(WS_VISIBLEでないウインドウで)処理を行う
この中で、TSR や VxD は、システムに依存した処理を行うときには有効ですが、 デーモンやWin32サービスが行うような処理とは分野が多少異なります。

○デーモンもどき
 Windows95からは、Windows 3.1では存在しなかったユーザログオン機能 (利用できるユーザとパスワードを設定できる)が設定できるようになったため、 単純に「スタートアップ」グループから起動させるアプリケーションをデーモンとは 言えないのですが、タスクバーのインジケータにアイコンを表示してウインドウを表示 しないアプリケーションとして、WindowsNTのWin32サービスと同様の処理を行う 「デーモンもどき」を作ることができます(注2)。
(注2)一般的に、アプリケーションをログオンと同時に実行 させるには、Windows 3.1ではプログラムマネージャの「スタートアップ」グループ、 Windows95ではタスクバーの「スタートアップ」フォルダに登録して実行させるか、 WIN.INIの [windows] セクションのload=xxxxx または run=xxxxx に実行ファイル名を書き込む。 デーモンはユーザに存在を知られず、こっそり動くことを考えれば、load=xxxxx に 登録するのがよいと思われるが、「デーモンもどき」は、ウインドウを表示しないので run=xxxxx でも構わない。

○ウィンドウを表示しないウィンドウスタイル
さて、ウインドウを表示せずにアプリケーションを実行させるには、 Win32 API CreateWindow() で指定するウインドウスタイルによって設定する ことができます。
Windows95にログオンして Visual C++ 4.x のスパイ++を実行してみると、 [Ctrl]+[Alt]+[Del]キーを押して表示される「プログラムの強制終了」ウインドウや、 タスクマネージャにも表示されないウインドウが多く存在することがわかります。  その中に、ウインドウ名 "Spooler Process" を持つ SPOOL32.EXE が あります。
このプロセスは、印刷が開始されたときに、インジケータ領域にプリンタのアイコン を表示します。また、印刷に障害が発生したときには、そのアイコンで障害が発生した ことユーザに知らせます。
SPOOL32.EXE のウインドウ "Spooler Process" のウインドウスタイル は
WS_CLIPSTRING | WS_BORDER | WS_DLGFRAME になっていますが、このスタイルを使えば、こっそりプロセスを実行することが できるわけです。

○タスクバーのインジケータアイコンを表示させる
タスクバーは、Windows95で初めて登場したウインドウです。 Windows95では、このウインドウを制御するために Win32 API Shell_NotifyIcon() が追加されました(表8-1)。
 先ほどと同様にスパイ++で、タスクバーウインドウを調べてみますと、
"Shell_TrayWnd" ... タスクバーのウインドウクラス(親)
 + "TrayNotifyWnd" ... インジケータ領域のウインドウクラス(子)
  + "TrayClockWClass" ... 時計のウインドウクラス(孫)
となっていることがわかります。
インジケータ領域のウインドウクラス名 "TrayNotifyWnd" で、 このウインドウのサイズを調べて、アイコンをクリックしたときに表示させる ポップアップメニューの位置を決めることができます。
タスクバーは画面の上下左右に位置を変更できるため、それによってメニューの 表示位置を調整する必要があります。
実際には、ディスプレイの解像度と "TrayNotifyWnd" ウインドウの長方形座標 を調べて、どの位置にメニューを表示するかを判断します。

☆ポップアップメニューの表示
 Windows 3.1では、TrackPopupMenu() でポップアップメニューを表示 していましたが、Windows95 では Win32 API TrackPopupMenuEx() が追加 されました。このAPIは TrackPopupMenu() とほとんど同様な動作をしますが、 メニューを表示させる位置を指定するフラグが強化されています (表8-2)。

☆WindowsNTでも実行するためにタスクバーの有無を判断する
Windows95アプリケーションをWindowsNTでも実行できるようにするためには、 どちらでも動作する API を使用して処理が行えるようにしなければなりません。
WindowsNT 3.51では、サポートしていない機能があってもとりあえず実行できます ので、OSチェックや機能チェックで、どちらでも動作させるようにすることが できます(注3)。
(注3)WindowsNT 3.5以前のバージョン上で Windows95 で新しくサポートされたAPIを 使用しているアプリケーションを実行させようとしても、実行することさえできない。
例えば、「デーモンもどき」をWindowsNT 3.51上でも実行させようと するとき、タスクバーがないため、アプリケーションは動作はしますが Windows95と 同様の動作はできません。
そこで、タスクバーがウインドウ上にあるかを判断することによってどちらのOSでも 動作ができるようにします(リスト8-2)。
リスト8-2の処理をOSチェックで処理を変えて対応させることもできますが、 WindowsNT にも Windows95 と同様のGUIを開発中であるため、タスクバーの有無で判断 するのが無難でないかと思います。


図8-5:画面上の"TrayNotifyWnd"の位置とメニューの位置関係
+-------------------------------+
|□-----+               +-----□|
||      |               |      ||
|+------+               +------+|
|       (1)    (2)        |
|               |               |
|               |               |
|          -----+-----          |
|               |               |
|               |               |
|        (3)    (4)         |
|+------+               +------+|
||      |               | メニュー ||
|□-----+               +-----□|
+-------------------------------+

    /* ディスプレイの解像度を得る */
    hdc = GetDC(hWnd);
    ptDisp.x = GetDeviceCaps(hdc, HORZRES);
    ptDisp.y = GetDeviceCaps(hdc, VERTRES);
    ReleaseDC(hWnd, hdc);

    /* 基本の位置は(4)*/
    /* メニューはウインドウ右上起点で下端そろえ */
    ptMenu.x = rtTrayNotify.right;
    ptMenu.y = rtTrayNotify.top;
    uFlag    = TPM_BOTTOMALIGN;                  /* 下端そろえ */

    /* (1)または(3)*/
    /* メニューはウインドウ左起点 */
    if(rtTrayNotify.left < (ptDisp.x / 2)){
        ptMenu.x = rtTrayNotify.left;            /* 左 */
    }

    /* (1)または(2)*/
    /* メニューはウインドウ下起点で上端そろえ */
    if(rtTrayNotify.top < (ptDisp.y / 2)){
        ptMenu.y = rtTrayNotify.bottom;          /* 下 */
        uFlag = TPM_TOPALIGN;                    /* 上端そろえ */
    }

表8-2:TrackPopupMenuEx
書  式 BOOL TrackPopupMenuEx(
HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm)
説  明  
引  数 HMENU hmenu ... 表示するメニューのハンドル
UINT fuFlags ... メニューを表示する位置を決定するフラグ
TrackPopupMenu() で指定できるフラグに TPM_HORIZONTAL, TPM_VERTICALが追加された。
int x ... メニューのX座標
int y ... メニューのY座標
HWND hwnd ... メッセージを送信するウインドウのハンドル
LPTPMPARAMS lptpm ... TPMPARAMS構造体を指すポインタ(NULLを指定できる)
注意事項 TPMPARAMS 構造体
DWORD cbSize ... 構造体のサイズ sizeof(TPMPARAMS)
RECT rc ... ポップアップメニューを消去せずにユーザーが選択できる座標を格納するRECT構造体


リスト8-2:タスクバーが存在しないときはアイコンウインドウを表示

/* タスクバーが存在しないときはアイコンウインドウを表示 */ if(FindWindow("Shell_TrayWnd", NULL) == NULL){ /* ウインドウを可視スタイルにする */ dwStyle |= WS_VISIBLE; }



8-6 Windows95のサービスプロセス

Windows95でのサービスプロセスは、Windows NT のようにはっきりしたもの でなく、画面を表示させないウインドウをもつプロセスをユーザがログオン する前に実行させるだけの、単純なものです。
実際には、
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion
\RunServices または \RunServicesOnce キーに
smp95d=smp95d.exe -service
のような項目を追加することによって実行ているものです。
ただし、\RunServicesOnce は、登録直後に起動された1度だけ有効に なります。これは、アプリケーションのセットアップ終了後、システムにロック されていたモジュールを上書きするといった用途に利用されます。

○シャットダウンかログオフか
プロセスをレジストリに書き込むだけでプロセスを実行したときは、シャットダウン されたか、ログオフされたかの判断ができません。
そこで、RegisterServiceProcess() でシステムにプロセスの登録を行う必要が あります。
プロセスをサービスとして登録することによって、WM_QUERYENDSESSION, WM_ENDSESSION メッセージの lParam でプロセスの状態を知ることが できます。
lParam が NULL ... シャットダウンしようとしている
EWX_REALLYLOGOFF ... ログオフしようとしている
ただし、このAPIは Windows95 のみ動作可能で、動的にエントリポイント を探して実行しなければなりません。



表8-3:RegisterServiceProcess
書 式 APIENTRY DWORD RegisterServiceProcess(DWORD dwProcessId, DWORD dwServiceType)
説 明 システムに、シンプルサービスプロセスを登録する。(Windows95でのみ利用可能)
引 数 DWORD dwProcessId ... 登録(解除)するプロセスID
NULLのときは、カレントプロセスになる。
DWORD dwServiceType ... サービスタイプ
RSP_SIMPLE_SERVICE ... シンプルサービスプロセスを登録する
RSP_UNREGISTER_SERVICE ... シンプルサービスプロセスの登録を解除する


リスト8-3:RegisterServiceProcess() の実行例

#define RSP_SIMPLE_SERVICE (DWORD)0x00000001 #define RSP_UNREGISTER_SERVICE (DWORD)0x00000000 DWORD RegisterServiceProcess(DWORD dwProcessId, DWORD dwServiceType) { HANDLE hKernel; DWORD (APIENTRY *lpfnRegisterServiceProcess)(DWORD, DWORD); /* Kernel32.dllは必ずロードされているため、LoadLibrary は行わない */ hKernel = GetModuleHandle("KERNEL32"); if((FARPROC)lpfnRegisterServiceProcess = GetProcAddress(hKernel, "RegisterServiceProcess")){ return lpfnRegisterServiceProcess(dwProcessId, dwServiceType); } return 0; }



8-7 プログラムについて

○ソースリスト \DAEMON
SOCKET.C(通信サーバ処理 WindowsNT/95共通)
CONNECT.C(通信サーバ処理 WindowsNT/95共通)
SERVICE.C(Win32サービスの処理)
WINDOW.C(「デーモンもどき」ウインドウの処理)
SMPD.H(共通ヘッダーファイル)
MAKEFILE.95(Windows95用メイクファイル)
MAKEFILE.NT(WindowsNT用メイクファイル)
TELNETクライアントは、ポート番号23のサーバであれば接続できますので、 サンプルデーモンを、TELNETクライアントでテストできるようにするために ポート番号32のサーバソケットをオープンさせています。
コンパイル方法は、以下のとおりです。
>nmake -f MAKEFILE.NT(WinodwsNT SERVICE.C main() がメイン関数)
>nmake -f MAKEFILE.95(Windows95 WINDOW.C WinMain()がメイン関数)

○使い方
サービスを起動し、TELNETクライアントでサービスを実行しているマシンに 接続すると、メッセージを表示して接続したことを知らせます。
接続後は、キー入力のエコーを表示し、改行キーでその行の入力を次の行に表示 します。セッションを切断させるときは、exit[Ret] を入力します。
☆WindowsNTのWin32サービスのインストール、アンインストール
インストールは、コマンドラインから
>smpntd -install
を実行し、コントロールパネルの「サービス」から、"Sample Daemon"を 起動します。
アンインストールは、、"Sample Daemon"の停止後に、コマンドラインから
>smpntd -uninstall
を実行します。


WindowsNT用SERVICE.Cの改定版が、拙著 実例で学ぶWin32API活用術 第23章 にあります。


WindowsNT4.0SP3以降対応 バグFIX情報:サンプルソース SERVICE.C 64行から、以下のように修正する。

switch(dwCtrlCode){
    case SERVICE_CONTROL_STOP:
        /* サービスが停止処理を開始したことを通知してから終了する */
        ReportSCManager(SERVICE_STOP_PENDING, NO_ERROR, 0);
        SetEvent(_hServDoneEvent);
        return;