Win32(NT)プログラミングノウハウ集
http://www02.so-net.ne.jp/~handa/deadend/trialWin32.html

1996/07/02

半荘

1.プロセスとスレッド

 プロセスのデフォルトのスレッドを、プライマリスレッドという。 プライマリスレッドが終了すると、そのプロセスも終了する。Win32のCreateProcess()はUNIXのfork(),exec()に対応する。

1.1 プロセス

 プロセスの生成の例を以下に示す。

DWORD dwErrorNumber;
LPCTSTR lpszImageName;       /* モジュール名のアドレス */
LPCTSTR lpszCommandLine;      /* コマンド ラインのアドレス */ 
LPSECURITY_ATTRIBUTES lpsaProcess; /* プロセスのセキュリティ属性のアドレス */ 
LPSECURITY_ATTRIBUTES lpsaThread;  /* スレッドのセキュリティ属性のアドレス */ 
BOOL fInheritHandles;        /* 新しいプロセスがハンドルを継承するか? */ 
DWORD fdwCreate;                   /* 作成フラグ */ 
LPVOID lpvEnvironment;       /* 新しい環境ブロックのアドレス */ 
LPCTSTR lpszCurDir;         /* 現在のディレクトリ名のアドレス */ 
STARTUPINFO siStartInfo;           /* STARTUPINFOのアドレス */ 
PROCESS_INFORMATION piProcInfo;    /* PROCESS_INFORMATIONのアドレス */ 

lpszImageName = NULL;
lpszCommandLine = "command argument1 argument2";
lpsaProcess = NULL;
lpsaThread = NULL;
fInheritHandles = FALSE;
fdwCreate = NORMAL_PRIORITY_CLASS;
lpvEnvironment = NULL;
lpszCurDir = NULL;
siStartInfo.cb = sizeof (siStartInfo);
siStartInfo.lpReserved = NULL;
siStartInfo.dwFlags = 0;
siStartInfo.cbReserved2 = 0;
siStartInfo.lpReserved2 = NULL;
siStartInfo.lpDesktop = NULL;
if (!CreateProcess(lpszImageName, lpszCommandLine, lpsaProcess, lpsaThread,
        fInheritHandles, fdwCreate, lpvEnvironment, lpszCurDir,
        &siStartInfo, &piProcInfo)){
    dwErrorNumber = GetLastError();
    MsgBox("CreateProcess(%ld) error.", dwErrorNumber);
    return dwErrorNumber;
}
 プロセスはExitProcess()で終わることが推奨されている。

1.2 スレッド

 スレッドの生成の例を以下に示す。

VOID fnThreadFunc(LPTSTR lpszBuffer){
    printf("%s\r\n", lpszBuffer);
    ExitThread(0);
}
    :
    HANDLE hThread;
    DWORD dwThreadID;
    DWORD dwErrorNumber;
    LPTSTR lpszBuffer;

    lpszBuffer = (LPTSTR)malloc(64);
    CopyMemory(lpszBuffer, "MEMORY TEST", 12);
    hThread = CreateThread(
                NULL,                                  // セキュリティ属性なし
                0,                                     // スタック・サイズ
                (LPTHREAD_START_ROUTINE)fnThreadFunc, // スレッド関数
                (LPVOID)lpszBuffer,                    // スレッドへの引き数
                0,                                     // スレッドを即時実行
                &dwThreadID);                          // スレッド識別子
    if (hThread == (HANDLE)NULL){
        dwErrorNumber = GetLastError();
        MsgBox("CreateThread(%ld) error.", dwErrorNumber);
        ExitThread(dwErrorNumber);
    }
    :
    CloseHandle(hThread);
 スレッドはExitThread()で終わることが推奨されている。

 マルチスレッドプログラムをビルドする際には、オプション/MTを指定しておく必要がある。

1.3 同期

 プロセスはExitProcess()によって終了する。しかし、マルチスレッドプログラムは、プライマリスレッドのみExitProcess()し、 他のスレッドはExitThread()するように作るとよい。

 スレッドの終了を待つには、WaitForSingleObject(),WaitForMultipleObjects()などが使える。 スレッドが実行中であるか終了しているかを知るには、GetExitCodeThread()を使う。

 以下は、複数スレッドの終了を待つサンプルプログラムである。ファイルの書き込み時にクリティカル・セクションを用い、 ファイルの整合性を確保している。また、マイクロソフト拡張の例外処理を行っている。

/*
** CreateThread.c - スレッド作成テストプログラム
** クリティカル・セクションを使わない(#undef USE_CRITICAL_SECTION)と、
** 実行時にSetFilePointer()でエラーになる。
*/
#define STRICT
#define USE_CRITICAL_SECTION
#include <windows.h>
#include <stdio.h>
#include <string.h>

static VOID fnInsert(LPCTSTR lpszExistingFile); 

DWORD dwErrorNumber;
CRITICAL_SECTION cs;    /* クリティカル・セクション・オブジェクト */

VOID main(INT argc, CHAR *argv[])
{
    INT l;
    HANDLE hThread[10];    /* スレッドハンドル配列 */
    DWORD dwThreadID;      /* スレッド識別子 */
    DWORD dwWait;

    if (argc != 2){
        printf("Usage: %s file-name-to-write\n", argv[0]);
        ExitProcess(0);
    }

#ifdef USE_CRITICAL_SECTION
    InitializeCriticalSection(&cs);
#endif
    for (l = 0; l < 10; l++){
        /* スレッド作成 */
        hThread[l] = CreateThread(
                        NULL, 
                        0,
                        (LPTHREAD_START_ROUTINE)fnInsert,
                        (LPVOID)argv[1],
                        0,
                        &dwThreadID);
        if (hThread[l] == NULL) {
            dwErrorNumber = GetLastError();
            printf("CreateThread() error(%ld).\n", dwErrorNumber);
            ExitProcess(dwErrorNumber);
        }
    }

    dwWait = WaitForMultipleObjects(10L, hThread, TRUE, INFINITE);

#ifdef USE_CRITICAL_SECTION
    DeleteCriticalSection(&cs);
#endif

    ExitProcess(0);
}

static VOID fnInsert(LPCTSTR lpszExistingFile)
{
    HANDLE hExistingFile;
    CHAR szDB[1024];
    SYSTEMTIME st; 
    DWORD dwBytesOccupied;

#ifdef USE_CRITICAL_SECTION
    __try{
    EnterCriticalSection(&cs);
#endif

    hExistingFile = CreateFile(
                        lpszExistingFile,
                        GENERIC_WRITE,
                        0,
                        NULL,
                        OPEN_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);

    GetLocalTime(&st);
    sprintf(szDB, "%4d/%02d/%02d %02d:%02d:%02d.%03d\n",
        st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); 

    if (SetFilePointer(hExistingFile, 0L, NULL, FILE_END) == 0xFFFFFFFF){
        dwErrorNumber = GetLastError();
        printf("SetFilePointer() error(%ld).\n", dwErrorNumber);
        CloseHandle(hExistingFile);
        ExitThread(dwErrorNumber);
    }

    if (!WriteFile(hExistingFile, szDB, strlen(szDB), &dwBytesOccupied, NULL)) {
        dwErrorNumber = GetLastError();
        if (dwErrorNumber != ERROR_IO_PENDING){
            printf("WriteFile() error(%ld).\n", dwErrorNumber);
            CloseHandle(hExistingFile);
            ExitThread(dwErrorNumber);
        }
    }

    CloseHandle(hExistingFile);

#ifdef USE_CRITICAL_SECTION
    }__finally{
    LeaveCriticalSection(&cs);
    }
#endif

    ExitThread(0);
}
2.メモリ

2.1 ヒープ

 Win32でプロセス固有のメモリを確保する場合、ヒープから確保するのが一般的である。メモリの確保は、以下の手順を取る。

 1. ヒープの作成

 2. ヒープからのメモリの割り当て

 3. メモリの使用

 4. メモリの解放

 5. ヒープの破棄

 メモリの解放はヒープの破棄によっても行われるので、必須ではない。 またヒープの作成はせず、プロセス・ヒープ(プロセスのデフォルトのヒープ)をオープンする方法もある。 この場合、ヒープの破棄はできない。

 以下にメモリ確保の例を示す。

    HANDLE hHeap;
    LPVOID lpvBuffer;
    DWORD dwBufferSize;
    /* ヒープの作成 */
    hHeap = HeapCreate(0L, 1024L, 0L);
    if (hHeap == NULL){
        dwErrorNumber = GetLastError();
        printf("HeapCreate(%ld) error.\r\n", dwErrorNumber);
        ExitProcess(dwErrorNumber);
    }
    /* メモリの割り当て */
    dwBufferSize = 65536L;
    lpvBuffer = HeapAlloc(hHeap, 0L, dwBufferSize);
    if (lpvBuffer == (LPVOID)NULL){    /* HeapAlloc()は失敗してもSetLastError()しない */
        printf("メモリの割り当てができません(サイズ: %ld)。\r\n", dwBufferSize);
        ExitProcess(1);
    }
    :
    if (!HeapFree(hHeap, 0, (LPVOID)lpsFirstAddress)){    /* HeapFree()は失敗してもSetLastError()しない */
        printf("メモリの解放ができません。\r\n");
    }
    if (!HeapDestroy(hHeap)){
        printf("ヒープの破棄ができません(%ld)。\r\n", GetLastError());
    }
2.2 バッファの境界合せ

 5章で説明するRawI/Oを行うには、読み書き用のバッファの先頭アドレスを ボリュームのセクタサイズに境界合せしなければならない。 これはアドレスを計算して自分で合わせる方法もあるし、ヒープ関数でなく仮想メモリ関数を使う方法もある。これらのやり方を以下に示す。

*自分で計算する方法

    /* バッファのアドレス境界合せのため、セクタサイズ取得 */
    {
        CHAR szCurrentDirectory[MAX_PATH];    /* ルート パス名 */ 
        DWORD dwFreeClusters;                 /* 空きクラスタ数 */ 
        DWORD dwClusters;                     /* クラスタ総数 */ 
        (VOID)GetCurrentDirectory(MAX_PATH, szCurrentDirectory);
        szCurrentDirectory[3] = '\0';         /* ドライブ名のみ切り出し */
        if (!GetDiskFreeSpace(szCurrentDirectory, &dwSectorsPerCluster, &dwBytesPerSector,
                &dwFreeClusters, &dwClusters)){
            printf("ルートパス名が間違っています。\r\n");
            ExitProcess(1);
        }
        dwAlignment = dwBytesPerSector;       /* セクタサイズ */
    }
    /* ヒープの作成 */
    hHeap = HeapCreate(0L, 1024L, 0L);
    if (hHeap == NULL){
        dwErrorNumber = GetLastError();
        printf("HeapCreate(%ld) error.\r\n", dwErrorNumber);
        ExitProcess(dwErrorNumber);
    }
    /* メモリの割り当て */
    dwBufferSize = 65536L;
    lpvFirstBuffer = HeapAlloc(hHeap, 0L, dwBufferSize);
    if (lpvFirstBuffer == (LPVOID)NULL){    /* HeapAlloc()は失敗してもSetLastError()しない */
        printf("メモリの割り当てができません(サイズ: %ld)。\r\n", dwBufferSize);
        ExitProcess(1);
    }
    /* アドレス境界合わせ */
    dwAddress = (DWORD)lpsFirstBuffer;
    dwAddSize = dwAddress % dwAlignment;
    if (dwAddSize > 0L){
        lpvFirstBuffer = HeapReAlloc(hHeap, HEAP_REALLOC_IN_PLACE_ONLY,
        lpvFirstBuffer, dwBufferSize + dwAddSize);
        if (lpvFirstBuffer == (LPVOID)NULL){
            printf("メモリの割り当てができません(サイズ: %ld)。\r\n", dwBufferSize + dwAddSize);
            ExitProcess(1);
        }
    }
    lpvNextBuffer = lpvFirstBuffer + dwAddSize;    /* ReadFile(),WriteFile()にlpvNextBufferを指定する */
*仮想メモリ関数を使う方法
    dwBufferSize = 65536L;
    lpvBuffer = VirtualAlloc((LPVOID)NULL, dwBufferSize, MEM_COMMIT, PAGE_READWRITE);
    if (lpvBuffer == (LPVOID)NULL)
        printf("VirtualAlloc(%ld) error.\r\n", GetLastError());
    :
    if (!VirtualFree(lpvBuffer, dwBufferSize, MEM_DECOMMIT))
        printf("VirtualFree(%ld) error.\r\n", GetLastError());
2.3 共有メモリ

 Win32には、ファイルをメモリに割り付けるファイルマッピング機能がある。 これを使って、いわゆる共有メモリを実装することができる。名前付き共有メモリの例を以下に示す。

    /* サーバ */
    {
        HANDLE hMapFile;
        DWORD dwErrorNumber;
        struct _fileInfo *lpvMapped;  /* 自製構造体 */
        hMapFile = CreateFileMapping((HANDLE)0xffffffff, NULL, PAGE_READWRITE, 0, dwMemSize, "SHARE_MEM");
        if (hMapFile == NULL){
            dwErrorNumber = GetLastError();
            printf("CreateFileMapping(%ld) error.\n", dwErrorNumber);
            return 1;
        }
        lpvMapped = (struct _fileInfo *)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        if (lpvMapped == (struct _fileInfo *)NULL){
            dwErrorNumber = GetLastError();
            printf("MapViewOfFile(%ld) error.\n", dwErrorNumber);
            return 1;
        }
        /* lpvMappedへ値の代入... */
    }

    /* クライアント */
    {
        HANDLE hMapFile;
        DWORD dwErrorNumber;
        struct _fileInfo *lpvMapped;
        hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE, "SHARE_MEM");
        if (hMapFile == (HANDLE)NULL){
            dwErrorNumber = GetLastError();
            MsgBox("OpenFileMapping(%ld) error.", dwErrorNumber);
            ExitThread(1);
        }
        lpvMapped = (struct _fileInfo *)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        if (lpvMapped == (struct _fileInfo *)NULL){
            dwErrorNumber = GetLastError();
            MsgBox("MapViewOfFile(%ld) error.", dwErrorNumber);
            ExitThread(1);
        }
        :
        (VOID)UnmapViewOfFile((LPVOID)lpvMapped);
        (VOID)CloseHandle(hMapFile);
    }
 CreateFileMapping()は、指定した名前のオブジェクトが存在しなければ新規に作成し、 存在すれば既存のオブジェクトをオープンする。

2.4 デバッグ関数

 他プロセスのメモリを読み書きするReadProcessMemory(), WriteProcessMemory()というものがある。これらはデバッグ用関数として位置づけられているが、積極的に使ってもよいようだ。 以下にこれらの使用例を示す。

 memoryServerは、ヒープに"Read ProcessMemory test"という文字列を保持してmemoryClientを呼ぶ。memoryClientは文字列の先頭を"Write"に書き換える。 memoryServerはmemoryClientの終了後、メモリを表示する。

/* memoryServer.c */
VOID main()
{
    HANDLE hHeap;
    LPVOID lpvMem;
    DWORD dwRet;
    DWORD dwPid;

    hHeap = HeapCreate(0, 1024, 0);
    lpvMem = HeapAlloc(hHeap, 0, 36);
    CopyMemory(lpvMem, "Read ProcessMemory test", 24);
    dwPid = GetCurrentProcessId();
    dwRet = Invoke("memoryClient", dwPid, (DWORD)lpvMem);  /* memoryClientの起動 */
    printf("memoryServer:Hit return key to stop.\r\n");
    (VOID)getchar();
    printf("memoryServer:[%s]\r\n", (LPTSTR)lpvMem);
    HeapFree(hHeap, 0, lpvMem);

    ExitProcess(0);
}

/* memoryClient.c */
VOID main(INT lArgc, CHAR *lpszArgv[])
{
    HANDLE hProcess;               /* メモリを読み取るプロシージャのハンドル */
    LPCVOID lpBaseAddress;         /* 読み取りを開始するアドレス */
    LPTSTR lpBuffer;               /* 読み取ったデータを格納するためのバッファのアドレス */
    DWORD cbRead;                  /* 読み取るバイト数 */
    DWORD dwNumberOfBytesRead;     /* 実際に読み取ったバイト数 */
    DWORD dwNumberOfBytesWrite;    /* 実際に書き込んだバイト数 */
    DWORD dwPid;
    DWORD dwBase;

    dwPid = (DWORD)atol(lpszArgv[1]);     /* 呼び出し側のプロセスID */
    dwBase = (DWORD)atol(lpszArgv[2]);    /* 対象メモリアドレス */
    printf("memoryClient:pid=%ld,base=%ld\r\n", dwPid, dwBase);
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if (hProcess == (HANDLE)NULL){
        printf("OpenProcess(%ld) error.\r\n", GetLastError());
        ExitProcess(1);
    }
    lpBaseAddress = (LPCVOID)dwBase;
    cbRead = 1024;
    lpBuffer = (LPTSTR)malloc(cbRead); 

    if (!ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer,
            cbRead, &dwNumberOfBytesRead)){
        printf("ReadProcessMemory(%ld) error.\r\n", GetLastError());
    }
    printf("memoryClient:[%s]\r\n", lpBuffer);
    if (!WriteProcessMemory(hProcess, lpBaseAddress, "Write",
            5, &dwNumberOfBytesWrite)){
        printf("WriteProcessMemory(%ld) error.\r\n", GetLastError());
    }

    free(lpBuffer);
    ExitProcess(0);
}
3.DLL

3.1 使い方

 DLLの使い方には、動的リンクと静的リンクがある。動的リンクは、 LoadLibrary()を使う方法で、静的リンクは、 dllimport/dllexportを使う方法である。それぞれの使い方を以下に示す。

*動的リンク

DLL側は.defファイルを作成し、関数を明示的にエクスポートする。呼び側は以下のようなコーディングを行う。

プログラム:
typedef DWORD (FAR WINAPI *MsgProc)(
    WORD fwEventType,
    DWORD dwIDEvent,
    WORD wStrings,
    LPCTSTR plpszStrings[],
    DWORD dwData,
    LPVOID lpvData
);

    :
    DWORD dwErrorNumber;
    MsgProc fnMessageProc;
    HMODULE hDllHandle;
    DWORD dwRet;
    WORD fwEventType;
    DWORD dwIDEvent;
    WORD wStrings;
    LPCTSTR plpszStrings[8];
    DWORD dwData;
    LPVOID lpvData;

    hDllHandle = (HMODULE)LoadLibraryEx("spgcomon.dll", NULL, 0);    /* ライブラリのロード */
    if (hDllHandle == (HMODULE)NULL){
        dwErrorNumber = GetLastError();
        printf("LoadLibrary(%ld) error.\n", dwErrorNumber);
        ExitProcess(1);
    }
    fnMessageProc = (MsgProc)GetProcAddress(hDllHandle, "ReportLog");    /* 関数アドレスの取得 */
    if (fnMessageProc == (MsgProc)NULL){
        dwErrorNumber = GetLastError();
        printf("GetProcAddress(%ld) error.\n", dwErrorNumber);
        ExitProcess(1);
    }
    fwEventType = EVENTLOG_INFORMATION_TYPE;
    dwIDEvent = MSG_SORT_IN;
    wStrings = 1;
    plpszStrings[0] = lpszFileName;
    dwData = strlen("eventLog.cの\n\t\rtestテストです。");
    lpvData = (LPVOID)malloc((INT)dwData);
    CopyMemory(lpvData, "eventLog.cの\n\t\rtestテストです。", dwData);
    dwRet = (fnMessageProc)(fwEventType, dwIDEvent, wStrings, plpszStrings, dwData, lpvData);    /* 呼び出し */
    if (!FreeLibrary((HINSTANCE)hDllHandle)){
        dwErrorNumber = GetLastError();
        printf("FreeLibrary(%ld) error.\n");
    }

defファイル:
;spgcomon.def
LIBRARY spgcomon
EXPORTS
ReportLog
*静的リンク

DLL側は.defファイルを作成せず、以下のようにDllExport宣言する。

#define DllExport __declspec( dllexport )
DllExport INT SortServer(struct grb*, DWORD);
呼び側は以下のようにDllImport宣言し、DLLの名前解決のために.libをリンクする。
#define DllImport __declspec( dllimport )
DllImport INT SortServer(struct grb*, DWORD);
あとは、通常の関数と同様に呼び出せる。
    INT lRet;
    DWORD dwNum;

    dwNum = WaitForSPG(lpvQgent);
    lRet = SortServer(lpvGrb, dwNum); 
3.2 DLL作成方法

 前節で説明した通り、呼び側がDLLを動的リンクするか静的リンクするかで少しの違いがある。

*共通事項

 DllMain()関数を用意する。DllMain()の基本型を以下に示す。

BOOL APIENTRY DllMain( HANDLE hModule, 
        DWORD dwReasonForCall, 
    LPVOID lpReserved )
{
    switch( dwReasonForCall) {
    case DLL_PROCESS_ATTACH:
        ...
    case DLL_THREAD_ATTACH:
        ...
    case DLL_THREAD_DETACH:
        ...
    case DLL_PROCESS_DETACH:
        ...
    }
    return TRUE;
}
 プロセスが初めてDLLをロードするとき、DLL_PROCESS_ATTACHが起こる。 DLLをロードしているプロセスが新しいスレッドを作るとき、DLL_THREAD_ATTACHが起こる。

 DLLを作成するとき、VC++で新規プロジェクトのDynamic-Link Libraryを選ぶ。これにより、コンパイラ、リンカの設定がDLL用になる。 一つ気をつけるべき点は、使用するランタイムライブラリである。 DLLがマルチスレッドで動くならば、ランタイムライブラリもマルチスレッド用をリンクしなければならない。

*動的リンク

 前節のように、.defファイルを用意し、プロジェクトに挿入しておく。.defファイルの内容は、以下の通り。

LIBRARY ライブラリ名
EXPORTS
エクスポートする関数名
:
*静的リンク

 DLL作成時にできるインポートライブラリ(.lib)を呼び側にリンクする。

*動的リンクと静的リンクの使い分け

4.イベントログ

 イベントログ出力を行うには、以下の手順でメッセージモジュールを作成する必要がある。

 1.メッセージファイルの作成

 2.メッセージファイルのコンパイル

 3.メッセージモジュールのビルド

 4.メッセージモジュールのレジストリへの登録

4.1 メッセージファイルの作成

 メッセージファイル(.mc)は、以下のような形式である。

LanguageNames=(Japanese=1041:MSG0041)
MessageIdTypedef=DWORD
SeverityNames=(
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR
)
FacilityNames=(
Informational=0xF01:FACILITY_INFORMATIONAL
Warning=0xF02:FACILITY_WARNING
Error=0xF03:FACILITY_ERROR
)
MessageId=6000
Severity=Informational
Facility=Informational
SymbolicName=MSG_SORT_GRDSSTR
Language=Japanese
ソート実行開始
.

MessageId=6001
Severity=Informational
Facility=Informational
SymbolicName=MSG_SORT_GRDSNEN
Language=Japanese
ソート正常終了
.
4.2 メッセージファイルのコンパイル

 メッセージファイルのコンパイルには、メッセージコンパイラ(mc)を用いる。 ここでは、メッセージファイル名を spgmess.mcと仮定する。コマンドラインから、

    > mc spgmess.mc

と入力すると、

    MSG0041.bin メッセージデータの実体(バイナリ。ファイル名はspgmess.mcで指定)

    spgmess.rc リソースファイル(MSG0041.binをインクルードしているだけ)

    spgmess.h C言語形式のインクルードファイル(メッセージID一覧)

の三つのファイルができる。

4.3 メッセージモジュールのビルド

 前節で説明したspgmess.rcはイベントログを行うユーザモジュールのプロジェクトに挿入する。 spgmess.hは、ユーザプログラムでインクルードする。

 イベントログ出力関数ReportEvent()の使用例を以下に示す。

    DWORD dwErrorNumber;
    CHAR szUNCServerName[16]; 
    CHAR szSourceName[256]; 
    HANDLE hEvent;
    WORD fwEventType;
    WORD fwCategory; 
    DWORD dwIDEvent;
    PSID pUserSid; 
    WORD wStrings;
    DWORD dwData;
    LPCTSTR plpszStrings[4];
    LPVOID lpvData;

    szUNCServerName[0] = '\0';    /* ローカルコンピュータを対象 */
    strcpy(szSourceName, "spgcomon");    /* ソース名 */

    hEvent = RegisterEventSource(szUNCServerName, szSourceName);
    if (hEvent == (HANDLE)NULL){
        dwErrorNumber = GetLastError();
        printf("RegisterEventSource(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }
    fwEventType = EVENTLOG_INFORMATION_TYPE;    /* イベントタイプ(情報、警告、エラー) */
    fwCategory = 0;                             /* メッセージカテゴリ */
    dwIDEvent = MSG_SORT_GRDSSTR;               /* MessageIdではなくSymbolicNameを指定 */
    pUserSid = (PSID)NULL;                      /* セキュリティ識別子 */
    wStrings = 0;                               /* メッセージの引数の数 */
    dwData = 0;                                 /* バイナリデータの長さ */
    plpszStrings[0] = NULL;                     /* メッセージの引数文字列 */
    lpvData = NULL;                             /* バイナリデータ */

    if (!ReportEvent(hEvent, fwEventType, fwCategory, dwIDEvent, pUserSid,
            wStrings, dwData, plpszStrings, lpvData)){
        dwErrorNumber = GetLastError();
        printf("ReportEvent(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }

    if (!DeregisterEventSource(hEvent)){
        dwErrorNumber = GetLastError();
        printf("DeregisterEventSource(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }
 ReportEvent()に指定するメッセージIDは、メッセージファイルのMessageIdでなく、 SymbolicNameであることに注意しなければならない。

4.4 メッセージモジュールのレジストリへの登録

 メッセージモジュール(.exeか.dll)は、そのままではイベントログ出力できない。 イベントビューアがイベントID(=メッセージID)を文字列にマップできるよう、 特定のレジストリにメッセージモジュール名などを登録しなければならない。このレジストリは以下である。

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application\ソース名

ここに登録する値は、以下の二つである。

前節のReportEvent()を行う前に、これらのレジストリをチェックするようコーディングすべきである。 例を以下に示す。
#define SPG_SUB_KEY "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\spgcomon" 

    HKEY hk;
    DWORD dwDisposition;
    DWORD dwLen;
    DWORD dwLevel;
    UCHAR szBuf[80];

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                        SPG_SUB_KEY,
                        0,
                        KEY_ALL_ACCESS,
                        &hk) != ERROR_SUCCESS){
        if (RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                            SPG_SUB_KEY,
                            0,
                            "spgcomon", // class name
                            REG_OPTION_NON_VOLATILE,
                            KEY_ALL_ACCESS,
                            NULL,
                            &hk,
                            &dwDisposition) != ERROR_SUCCESS){
            dwErrorNumber = GetLastError();
            printf("RegOpenKeyEx & RegCreateKeyEx(%ld) error.\n", dwErrorNumber);
            return dwErrorNumber;
        }
    }

    dwLen = sizeof (szBuf);
    if (RegQueryValueEx(hk,
                        (LPTSTR)"EventMessageFile",
                        (LPDWORD)NULL,
                        (LPDWORD)NULL,
                        (LPBYTE)szBuf,
                        &dwLen) != ERROR_SUCCESS){
        strcpy(szBuf, "h:\\spgph1\\bin\\spgcomon.dll");
        if (RegSetValueEx(hk,                      /* subkey handle */
                            "EventMessageFile",    /* value name */
                            0,                     /* must be zero */
                            REG_EXPAND_SZ,         /* value type */
                            (LPBYTE)szBuf,         /* address of value data */
                            strlen(szBuf) + 1) != ERROR_SUCCESS){    /* length of value data */
            dwErrorNumber = GetLastError();
            printf("RegQueryValueEx & RegSetValueEx(%ld) error.\n", dwErrorNumber);
            return dwErrorNumber;
        }
    }

    dwLen = sizeof (DWORD);
    if (RegQueryValueEx(hk,
                        (LPTSTR)"TypesSupported",
                        (LPDWORD)NULL,
                        (LPDWORD)NULL,
                        (LPBYTE)&dwLevel,
                        (LPDWORD)&dwLen) != ERROR_SUCCESS){
        dwLevel = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
        if (RegSetValueEx(hk,                    /* subkey handle */
                            "TypesSupported",    /* value name */
                            0,                   /* must be zero */
                            REG_DWORD,           /* value type */
                            (LPBYTE)&dwLevel,    /* address of value data */
                            dwLen) != ERROR_SUCCESS){    /* length of value data */
            dwErrorNumber = GetLastError();
            printf("RegQueryValueEx & RegSetValueEx(%ld) error.\n", dwErrorNumber);
            return dwErrorNumber;
        }
    }

    if (RegCloseKey(hk) != ERROR_SUCCESS){
        dwErrorNumber = GetLastError();
        printf("RegCloseKey(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }
5.ファイルI/O

 ファイルI/Oを行うための関数を以下に示す。

5.1 同期

 同期I/Oとは、I/O処理中はその他の処理がブロックされる、そのI/Oのことである。

 以下にReadFile()による同期Readの例を示す。

    HANDLE hFile;
    DWORD dwFileSize;    /* 読み出すファイルサイズ */
    DWORD dwRead;        /* 実際に読み込んだバイト数 */
    LPVOID lpvBuffer;    /* 読み込みバッファ */
    hFile = CreateFile(szFileName,                /* ファイル名 */
                        GENERIC_READ,             /* アクセス種類 */
                        0,                        /* 共有方法 */
                        NULL,                     /* セキュリティ属性 */
                        OPEN_EXISTING,            /* 作成方法 */
                        FILE_ATTRIBUTE_NORMAL,    /* ファイル属性 */
                        NULL);                    /* テンプレートファイルのハンドル */
    if (hFile == (HANDLE)0xffffffff){
        dwErrorNumber = GetLastError();
        printf("CreateFile(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }
    dwFileSize = 65536;
    dwBufferSize = 4096;
    lpvBuffer = malloc(dwBufferSize);
    for (dwSum = 0; dwSum < dwFileSize; dwSum += dwRead){
        dwBufferSize = (dwFileSize - dwSum < dwBufferSize ? dwFileSize - dwSum : dwBufferSize);
        if (!ReadFile(hFile, lpvBuffer, dwBufferSize, &dwRead, NULL)){
            dwErrorNumber = GetLastError();
            printf("ReadFile(%ld) error.\n", dwErrorNumber);
            CloseHandle(hFile);
            return dwErrorNumber;
        }
    }
    CloseHandle(hFile);
5.2 非同期

 非同期I/Oとは、同期I/Oと違い、I/O処理と平行にその他の処理を行える、そのI/Oのことである。

 非同期I/Oを行う場合のReadFile()とReadFileEx()の違い(Writeも同様)は、I/Oが完了したとき、前者はOVERLAPPED 構造体で指定したイベントがシグナル状態になり、後者は指定したI/O完了ルーチンが別スレッドで実行されることである。

 以下にWriteFileEx()による非同期Writeの例を示す。

/* ファイルI/O完了ルーチン */
VOID WINAPI FileIOCompletionRoutine(DWORD dwError, DWORD dwTransferred, LPOVERLAPPED lpo)
{
    /* dwError: 完了コード */
    /* dwTransferred: 転送するバイト数 */
    /* lpo: 入出力情報を持つ構造体のアドレス */
    printf("I/O完了状態: %ld\n", dwError);
    printf("転送されたバイト数: %ldバイト\n", dwTransferred);
}

    :
    HANDLE hFile;
    DWORD dwFileSize;    /* 書き出すファイルサイズ */
    hFile = CreateFile(szFileName,               /* ファイル名 */
                        GENERIC_WRITE,           /* アクセス種類 */
                        0,                       /* 共有方法 */
                        NULL,                    /* セキュリティ属性 */
                        CREATE_ALWAYS,           /* 作成方法 */
                        FILE_FLAG_OVERLAPPED,    /* ファイル属性 */
                        NULL);                   /* テンプレートファイルのハンドル */
    if (hFile == (HANDLE)0xffffffff){
        dwErrorNumber = GetLastError();
        printf("CreateFile(%ld) error.\n", dwErrorNumber);
        return dwErrorNumber;
    }
    dwFileSize = 65536;
    dwBufferSize = 4096;
    for (dwSum = 0; dwSum < dwFileSize; dwSum += dwBufferSize){
        dwBufferSize = (dwFileSize - dwSum < dwBufferSize ? dwFileSize - dwSum : dwBufferSize);
        /* 書き込み開始位置を決める */
        Overlapped.Offset = dwSum;

        if (!WriteFileEx(hFile, lpsNextAddress, dwBufferSize, &Overlapped, FileIOCompletionRoutine)){
            dwErrorNumber = GetLastError();
            printf("WriteFileEx(%ld) error.\n", dwErrorNumber);
            CloseHandle(hFile);
            return dwErrorNumber;
        }
        SleepEx(INFINITE, TRUE);    /* writeが完了すると勝手に起きる */
    }
    CloseHandle(hFile); 

 上記プログラムにおいて、SleepEx()を使用している理由が二つある。以下に示す。


5.3 アペンド

 UNIXのfopen(fileName, "a")のようなアペンドモードは、Win32のCreateFile()にはない。アペンドを行うには SetFilePointer()を用い、ファイルポインタをファイルの終端に移動させなければならない。

 アペンドの例を以下に示す。

    hFile = CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    /* OPEN_EXISTING必須 */
    SetFilePointer(hFile, 0, (PLONG)NULL, FILE_END);            /* FILE_END:ファイル終端 */
    WriteFile(hFile, lpvBuffer, dwBufferSize, &dwWrite, NULL);
5.4 部分書き換え

 CreateFile()にOPEN_EXISTINGを指定することにより、ファイルの部分書き換え(=ランダムアクセス)を行うこと ができる。当然、書き換えを行うファイルは存在していなければならない。

 部分書き換えの例を以下に示す。

    hFile = CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL,
                OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);  /* OPEN_EXISTING必須 */
    SetFilePointer(hFile, 1024, (PLONG)NULL, FILE_BEGIN);
    WriteFile(hFile, lpvBuffer, dwBufferSize, &dwWrite, NULL);
5.5 RawI/O

 Win32においてRawI/Oといったとき、狭義には、ディスクキャッシュを介さずにファイルをオープンする CreateFile()のFILE_FLAG_NO_BUFFERING指定のことを指し、広義にはCreateFile()に物理ディスク名、論理ドライ ブ名を指定することを指す。

5.5.1 FILE_FLAG_NO_BUFFERING

 このフラグは、I/O時にディスクキャッシュを使わなくなり、大抵の場合このフラグを指定しない場合より1〜2割高速である。  ただし、このフラグの使用には、以下の制限がある。

5.5.2 物理ディスク名

 CreateFile()に対する物理ディスク名指定方法を以下に示す。

"\\.\PHYSICALDRIVE0" ディスク0

 もちろんC言語では"\\\\.\\PHYSICALDRIVE0"と書く必要がある。この方法を使うと、ディスクはただ接続するだけでよく、 パーティションを切ったり、フォーマットする必要はない。(物理的に認識されていればよい)

5.5.3 論理ドライブ名

 CreateFile()に対する論理ドライブ名指定方法を以下に示す。

"\\.\C:" ドライブC

 もちろんC言語では"\\\\.\\C:"と書く必要がある。この方法を使うと、ディスクはパーティションを切るだけでよく、 フォーマットする必要はない。(論理的に認識されていればよい)

6.名前つきパイプ

 パイプはプロセス間通信をファイルのように扱えるので便利である。パイプには名前なしパイプと名前つきパイプがあり、 名前つきパイプの方が扱いやすい。パイプの名前は、例えば"\\.\PIPE\PIPE_TEST"となる。

 名前つきパイプのオブジェクトを作成する方をサーバといい、サーバが作成したオブジェクトを利用する方をクライアント という。サーバでパイプを作るとき、データの流れる方向、データ単位などを決める。データ単位にはメッセージ・モードと バイト・モードがあり、前者は例えば三回送信したら三回受信しなければならず、後者は一回ですべてのデータを受信できる。

 クライアントとサーバの例を以下に示す。

    /* サーバ */
    sprintf(szPipeName, "\\\\.%s", PIPE_NAME);
    hPipe = CreateNamedPipe(
                        szPipeName,                  /* パイプ名 */
                        PIPE_ACCESS_DUPLEX,          /* 双方向 */
                        PIPE_WAIT                    /* ブロッキング・モード */
                        | PIPE_READMODE_BYTE         /* バイト・モード */
                        | PIPE_TYPE_BYTE,
                        PIPE_UNLIMITED_INSTANCES,    /* インスタンス数の制限なし */
                        dwBufferSize,                /* 出力バッファ・サイズ */
                        dwBufferSize,                /* 入力バッファ・サイズ */
                        TIME_OUT,                    /* タイムアウト */
                        NULL);                       /* セキュリティ属性なし */
    if (hPipe == INVALID_HANDLE_VALUE) {
        dwErrorNumber = GetLastError();
        printf ("CreateNamedPipe(%ld) error.\r\n", dwErrorNumber);
        ExitProcess(dwErrorNumber);
    }

    /* クライアントが接続するまで待つ */
    if (!ConnectNamedPipe(hPipe, NULL)) {
        dwErrorNumber = GetLastError();
        printf("ConnectNamedPipe(%ld) error.\r\n", dwErrorNumber);
        CloseHandle(hPipe);
        ExitProcess(dwErrorNumber);
    }

    /* ワークのバッファ */
    lpszBuffer = (LPTSTR)malloc(dwBufferSize);
    if (lpszBuffer == (LPTSTR)NULL){
        printf("メモリ不足です。\r\n");
        ExitProcess(1);
    }

    /* 名前付きパイプからデータを読み込む */
    for (dwSum = 0; dwSum < dwDataSize; dwSum += dwInBytesOccupied){
        dwBufferSize = (dwDataSize - dwSum < dwBufferSize ? dwDataSize - dwSum : dwBufferSize);
        if (!ReadFile(hPipe, lpszBuffer, dwBufferSize, &dwInBytesOccupied, NULL)) {
            dwErrorNumber = GetLastError();
            switch (dwErrorNumber) {
            case ERROR_BROKEN_PIPE:
                printf("クライアントとの接続が切断されました。(%ld/%ld)\r\n",
                    dwSum, dwDataSize);
                break;
            default:
                printf("ReadFile(%ld) error (%ld/%ld)\r\n", dwErrorNumber,
                    dwSum, dwDataSize);
                break;
            }
            goto LABEL_last;
        }
    }

    /* クライアント */
    HANDLE hPipe;
    hPipe = CreateFile(szPipeName,                             /* パイプ名 */
                        GENERIC_WRITE | GENERIC_READ,          /* 読み書きアクセス */
                        FILE_SHARE_READ | FILE_SHARE_WRITE,    /* 読み書きアクセス共有 */
                        NULL,                                  /* セキュリティ属性なし */
                        OPEN_EXISTING,                         /* 既存の名前付きパイプに接続 */
                        FILE_ATTRIBUTE_NORMAL,                 /* ファイル属性なし */
                        NULL);                                 /* テンプレート・ファイルなし */
    if (hPipe == INVALID_HANDLE_VALUE) {
        dwErrorNumber = GetLastError();
        switch (dwErrorNumber) {
        case ERROR_FILE_NOT_FOUND:
            printf("CreateFile() に指定された名前付きパイプが見つかりません\r\n");
            break;
        default:
            printf ("CreateFile(%ld) error.\r\n", dwErrorNumber);
            break;
        }
        return 1;
    }
    /* 分割送信 */
    for (dwSum = 0; dwSum < dwDataSize; dwSum += dwByteW){
        dwBufferSize = (dwDataSize - dwSum < dwBufferSize ? dwDataSize - dwSum : dwBufferSize);
        if (!WriteFile(hPipe, lpszBuffer, dwBufferSize, &dwByteW, NULL)){
            dwErrorNumber = GetLastError();
            printf("WriteFile(%ld) error.\r\n", dwErrorNumber);
            CloseHandle(hFile);
            goto LABEL_exit;
        }
    }
    CloseHandle(hPipe);
 パイプを使うときの注意点を以下に示す。 7.サービス

 サービスはシステム常駐型のプログラムであり、 UNIXでいうところのデーモン(daemon)である。起動/停止は コントロール・パネルの中で行う。NTが起動したとき、特定のサービスを起動させることもできる。サービスを作成するには 規約があり、それにのっとってコーディングを行う。システムへのサービスの登録/削除はインストーラを用意すべきである。

 サービスプログラムには、以下の部分がある。

 1. プログラムのメイン

 2. 制御ハンドラ

 3. サービスのメイン

 4. サービスのスレッド

7.1 プログラムのメイン

 プログラムのメインは、サービスプロセスのメインスレッドをサービス制御マネージャに接続する。

 以下のプログラムにおいて、ServiceMainはサービスのメイン関数である。

VOID main()
{
    DWORD dwErrorNumber;
    SERVICE_TABLE_ENTRY DispatchTable[] = {
        {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
        {NULL, NULL}};    // 最後

    // メインスレッドをサービス制御マネージャに接続
    if (! StartServiceCtrlDispatcher(DispatchTable)){
        dwErrorNumber = GetLastError();
        MsgBox("StartServiceCtrlDispatcher(%ld) error.", dwErrorNumber);
    }
    ExitProcess(0);
}
7.2 制御ハンドラ

 制御ハンドラは、サービスプロセスのために特定のサービスの制御処理を行う。制御ハンドラの関数名は、ユーザが 定義する。制御処理の内容は、サービスの休止、休止の再開、サービスの停止などである(プログラマーズリファレンス のHandlerを参照)。制御ハンドラは、制御処理を終えたら現在の状態をサービス制御マネージャに報告しなければならない。

VOID ServiceCtrl(DWORD CtrlCode)
{
    DWORD State = SERVICE_RUNNING;    // サービス状態
    DWORD ChkPoint = 0;               // チェック・ポイント値
    DWORD Wait = 0;                   // 処理待機時間(ミリ秒)
    DWORD dwErrorNumber;
    MsgProc fnMessageProc;
    HMODULE hDllHandle;
    DWORD dwRet;
    WORD fwEventType;
    DWORD dwIDEvent;
    WORD wStrings;
    LPCTSTR plpszStrings[1];
    DWORD dwData;
    LPVOID lpvData;
    hDllHandle = (HMODULE)LoadLibraryEx("spgcomon.dll", NULL, 0);
    if (hDllHandle == (HMODULE)NULL){
        dwErrorNumber = GetLastError();
        MsgBox("LoadLibrary(%ld) error.", dwErrorNumber);
    }
    fnMessageProc = (MsgProc)GetProcAddress(hDllHandle, "ReportLog");
    if (fnMessageProc == (MsgProc)NULL){
        dwErrorNumber = GetLastError();
        MsgBox("GetProcAddress(%ld) error.", dwErrorNumber);
    }

    /* 制御コード別にディスパッチ */
    switch(CtrlCode) {
    /* サービスの休止 */
    case SERVICE_CONTROL_PAUSE:
        if (SvcStatus1.dwCurrentState == SERVICE_RUNNING) {
            SuspendThread(hThreadMaker);
            State = SERVICE_PAUSED;
        }
        break;
    /* 休止サービスの再開 */
    case SERVICE_CONTROL_CONTINUE:
        if (SvcStatus1.dwCurrentState == SERVICE_PAUSED) {
            ResumeThread(hThreadMaker);
            State = SERVICE_RUNNING;
        }
        break;
    /* サービスの停止 */
    case SERVICE_CONTROL_STOP:
        fwEventType = EVENTLOG_INFORMATION_TYPE;
        dwIDEvent = MSG_STUB_STOP;
        wStrings = 1;
        plpszStrings[0] = SERVICE_NAME;
        dwData = 0;
        lpvData = (LPVOID)NULL;
        dwRet = (fnMessageProc)(fwEventType, dwIDEvent, wStrings, plpszStrings, dwData, lpvData);
        State = SERVICE_STOP_PENDING;
        ChkPoint = 1;
        Wait = 3000;
        SetEvent(hStopEvent);
        break;
    /* サービス状態の更新 */
    case SERVICE_CONTROL_INTERROGATE:
        break;
    /* 無効な制御コード */
    default:
        break;
    }
    /* 更新されたサービス状態をサービス制御マネージャに通知 */
    ReportStatus(State, ChkPoint, Wait);
}

BOOL ReportStatus(DWORD State, DWORD ChkPoint, DWORD Wait)
{
    BOOL status;
    DWORD AllControl = (SERVICE_ACCEPT_PAUSE_CONTINUE |
                        SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); 

    /* サービス情報の設定 */
    SvcStatus1.dwCurrentState = State;
    if (State == SERVICE_START_PENDING) 
        SvcStatus1.dwControlsAccepted = 0;
    else
        SvcStatus1.dwControlsAccepted = AllControl;
    SvcStatus1.dwCheckPoint = ChkPoint;
    SvcStatus1.dwWaitHint = Wait;

    /* サービス情報を通知 */
    status = SetServiceStatus(hSvcStatus1, &SvcStatus1);
    if (!status){
        MsgBox("SetServiceStatus(%ld) error.", GetLastError());
        AbortService();
    }
    return status;
}
7.3 サービスのメイン

 サービスのメインは、サービスのエントリポイントの関数である。関数名は、ユーザが定義する。サービスのメインは、 順に以下の処理を行う。

 1. 制御ディスパッチャに制御ハンドラを登録する。

 2. サービスのスレッドを作る。

 3. 状態情報をサービス制御マネージャに知らせる。

 4. サービスの停止を待つ。

 以下のプログラムは、名前つきパイプのサーバ・サービスである。複数クライアントに対応するため、スレッドを二段階に 生成している。(ServiceThreadMaker()とServiceThreadEntity())

/* サービスのスレッドを作るスレッド */
VOID ServiceThreadMaker()
{
    DWORD dwErrorNumber;
    SECURITY_ATTRIBUTES SecAttrib;    // セキュリティ属性
    DWORD dwThreadID;
    HANDLE hThread;
    HANDLE hPipe;           // パイプ・ハンドル
    CHAR szPipeName[16];    // 名前付きパイプの名前
    BOOL fConnected; 

    /* 名前付きパイプ用のセキュリティ記述子の初期化 */
    SecDesc = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
    if (SecDesc == NULL) {
        MsgBox("LocalAlloc(%ld) error.", GetLastError());
        AbortService();
    return;
    }
    if (!InitializeSecurityDescriptor(SecDesc, SECURITY_DESCRIPTOR_REVISION)) {
        MsgBox("InitializeSecurityDescriptor(%ld) error.", GetLastError());
        AbortService();
        return;
    }

    /* NULL 随意 ACL を指定してアクセスをすべて許可 */
    if (!SetSecurityDescriptorDacl(SecDesc, TRUE, (PACL)NULL, FALSE)) {
        MsgBox("SetSecurityDescriptorDacl(%ld) error.", GetLastError());
        AbortService();
        return;
    }
    SecAttrib.nLength = sizeof (SecAttrib);
    SecAttrib.lpSecurityDescriptor = SecDesc;
    SecAttrib.bInheritHandle = TRUE; 

    dwBufferReg = GetMemmxFromRegistry("st_memmx");
    dwBufferReg = (dwBufferReg == 0 ? DEFAULT_BUFFER_SIZE : dwBufferReg);
    sprintf(szPipeName, "\\\\.%s", PIPE_NAME);

    /* クライアントが接続するまで待つ */
    while (TRUE){
        /* パイプハンドルは毎回作る */
        hPipe = CreateNamedPipe(
                        szPipeName,                  /* パイプ名 */
                        PIPE_ACCESS_DUPLEX,          /* 双方向 */
                        PIPE_WAIT                    /* ブロッキング・モード */
                        | PIPE_READMODE_BYTE         /* バイト・モード */
                        | PIPE_TYPE_BYTE,
                        PIPE_UNLIMITED_INSTANCES,    /* インスタンス数の制限なし */
                        dwBufferReg * 1024,          /* 出力バッファ・サイズ */
                        dwBufferReg * 1024,          /* 入力バッファ・サイズ */
                        TIME_OUT,                    /* タイムアウト */
                        &SecAttrib);                 /* セキュリティ属性 */
        if (hPipe == INVALID_HANDLE_VALUE) {
            dwErrorNumber = GetLastError();
            MsgBox("spgdrivr:CreateNamedPipe(%ld) error.", dwErrorNumber);
            ExitProcess(dwErrorNumber);
        }

        fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE :
                        (GetLastError() == ERROR_PIPE_CONNECTED);
        if (fConnected){
            /* サービス提供用スレッドの作成 */
            hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServiceThreadEntity,
                                    (LPVOID)hPipe, 0, &dwThreadID);
            if (hThread == (HANDLE)NULL){
                dwErrorNumber = GetLastError();
                MsgBox("CreateThread(%ld) error.", dwErrorNumber);
                CloseHandle(hPipe);
                AbortService();
                ExitThread(dwErrorNumber);
            }
        }else
            CloseHandle(hPipe);
    }
    ExitThread(0);
}

/* サービスのスレッドの実体 */
VOID ServiceThreadEntity(LPVOID lpvPipe)
{
    INT i;
    DWORD dwErrorNumber;
    HANDLE hMapFile;
    LPVOID lpvMapped;
    DWORD dwMemSize;             /* メモリサイズ */
    DWORD dwSum;                 /* 実際に読み込んだデータ合計 */
    LPTSTR lpszBuffer;           /* 送受信用メモリのアドレス */
    LPTSTR lpszBufferRoot;       /* 送受信用メモリの先頭アドレス */
    LPTSTR lpszTmp;              /* ワーク */
    DWORD dwInBytesOccupied;     /* 入力バイト数 */
    DWORD dwOutBytesOccupied;    /* 出力バイト数 */
    DWORD dwBufferSize;
    HANDLE hPipe;

    hPipe = (HANDLE)lpvPipe;

    /* ソートサーバの共有メモリをアタッチする */
    hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE, SHARED_MEMORY_NAME);
    if (hMapFile == (HANDLE)NULL){
        dwErrorNumber = GetLastError();
        MsgBox("spgdrivr:OpenFileMapping(%ld) error.", dwErrorNumber);
        ExitThread(1);
    }
    lpvMapped = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    if (lpvMapped == NULL){
        dwErrorNumber = GetLastError();
        MsgBox("spgdrivr:MapViewOfFile(%ld) error.", dwErrorNumber);
        ExitThread(1);
    }

    pfileInfo = (struct _fileInfo *)lpvMapped; 

    /* 送受信データの合計を出す */
    dwMemSize = 0;
    for (i = 0; i < pfileInfo->wInputFile; i++){
        struct _input *p;
        p = (struct _input *)((DWORD)pfileInfo + pfileInfo->wInputOffset + 
                sizeof (struct _input) * i);
        dwMemSize += p->dwSize;
    }

    /* ワークのバッファ */
    lpszTmp = (LPTSTR)malloc(dwBufferReg * 1024);
    if (lpszTmp == (LPTSTR)NULL){
        MsgBox("spgdrivr:メモリ不足です。(%dbytes)", dwBufferReg * 1024);
        ExitThread(1);
    }
    /* 一括処理用メモリ確保 */
    lpszBufferRoot = lpszBuffer = (LPTSTR)malloc(dwMemSize);
    if (lpszBuffer == (LPTSTR)NULL){
        MsgBox("spgdrivr:メモリ不足です。(%dbytes)", dwMemSize);
        ExitThread(1);
    }

    /* 名前付きパイプからデータを読み込む */
    dwBufferSize = dwBufferReg * 1024;
    for (dwSum = 0; dwSum < dwMemSize; dwSum += dwInBytesOccupied){
        dwBufferSize = (dwMemSize - dwSum < dwBufferSize ? dwMemSize - dwSum : dwBufferSize);
        if (!ReadFile(hPipe, lpszTmp, dwBufferSize, &dwInBytesOccupied, NULL)) {
            dwErrorNumber = GetLastError();
            switch (dwErrorNumber) {
            case ERROR_BROKEN_PIPE:
                MsgBox("spgdrivr:クライアントとの接続が切断されました。%ld", dwSum);
                break;
            default:
                MsgBox("spgdrivr:ReadFile() のエラー戻り値= %ld", dwErrorNumber);
                break;
            }
            goto LABEL_last;
        }
        CopyMemory(lpszBuffer, lpszTmp, dwInBytesOccupied);
        if (dwSum + dwInBytesOccupied < dwMemSize)
            lpszBuffer += dwInBytesOccupied;
    }

    lpszBuffer = lpszBufferRoot;
    dwBufferSize = (dwBufferReg * 1024 < dwSum ? dwBufferReg * 1024 : dwSum);
    CopyMemory(lpszTmp, lpszBuffer, dwBufferSize);
    for (dwSum = 0; dwSum < dwMemSize; dwSum += dwOutBytesOccupied){
        dwBufferSize = (dwMemSize - dwSum < dwBufferSize ? dwMemSize - dwSum : dwBufferSize);
        if (!WriteFile(hPipe, lpszTmp, dwBufferSize, &dwOutBytesOccupied, NULL)) {
            dwErrorNumber = GetLastError();
            switch (dwErrorNumber) {
            case ERROR_NO_DATA:
                MsgBox("spgdrivr:書き込めません。クライアントとの接続は切断されています。 %ld", dwSum);
                break;
            default:
                MsgBox("spgdrivr:WriteFile() のエラー戻り値=%ld", dwErrorNumber);
                break;
            }
            goto LABEL_last;
        }
        if (dwSum + dwOutBytesOccupied < dwMemSize){
            DWORD dwCopySize;
            lpszBuffer += dwOutBytesOccupied;
            dwCopySize = (dwMemSize - (dwSum + dwOutBytesOccupied) < dwBufferReg * 1024 ?
                            dwMemSize - (dwSum + dwOutBytesOccupied) : dwBufferReg * 1024);
            CopyMemory(lpszTmp, lpszBuffer, dwCopySize);
        }
    }

    /* パイプ中のデータがスタブによってすべて読み込まれるまでブロックする */
    FlushFileBuffers(hPipe); 

LABEL_last:
    /* 名前付きパイプを閉じ、スレッドを終了する */
    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);
    free(lpszBufferRoot);
    free(lpszTmp);
    ExitThread(0);
}

/* サービスのメイン */
VOID ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
    DWORD dwErrorNumber;
    DWORD dwThreadID;

    /* 制御ハンドラ関数の登録 */
    hSvcStatus1 = RegisterServiceCtrlHandler(
                    SERVICE_NAME,
                    (LPHANDLER_FUNCTION)ServiceCtrl);
    if (hSvcStatus1 == (SERVICE_STATUS_HANDLE)NULL) {
        dwErrorNumber = GetLastError();
        MsgBox("制御ハンドラ関数を登録できません。戻り値=%d", dwErrorNumber);
        return;
    }

    dwErrorNumber = GetFnameFromRegistry("ServiceErrorLog"); 

    /* サービス状態に依存しないサービス情報の設定 */
    SvcStatus1.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    SvcStatus1.dwWin32ExitCode = NO_ERROR;
    SvcStatus1.dwServiceSpecificExitCode = 0;

    /* サービス起動中であることをサービス制御マネージャに通知 */
    if (!ReportStatus(SERVICE_START_PENDING, 1, 8000)) return;

    /* サービス停止を指示するためのイベントを作成 */
    hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hStopEvent == NULL) {
        MsgBox("CreateEvent(%ld) error.", GetLastError());
        AbortService();
        return;
    }

    /* サービス停止用イベントの作成をサービス制御マネージャに通知 */
    if (!ReportStatus(SERVICE_START_PENDING, 2, 8000)) return;

    /* サービス提供用スレッドの作成 */
    hThreadMaker = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServiceThreadMaker,
                        NULL, 0, &dwThreadID);
    if (hThreadMaker == (HANDLE)NULL) {
        MsgBox("CreateThread(%ld) error.", GetLastError());
        AbortService();
        return;
    }

    /* サービス・スレッドの実行開始をサービス制御マネージャに通知 */
    if (! ReportStatus(SERVICE_RUNNING, 0, 1000)) return;

    /* サービスの起動をイベントログに出力 */
    {
        MsgProc fnMessageProc;
        HMODULE hDllHandle;
        DWORD dwRet;

        WORD fwEventType;
        DWORD dwIDEvent;
        WORD wStrings;
        LPCTSTR plpszStrings[1];
        DWORD dwData;
        LPVOID lpvData;

        hDllHandle = (HMODULE)LoadLibraryEx("spgcomon.dll", NULL, 0);
        if (hDllHandle == (HMODULE)NULL){
            dwErrorNumber = GetLastError();
            MsgBox("LoadLibrary(%ld) error.", dwErrorNumber);
        }
        fnMessageProc = (MsgProc)GetProcAddress(hDllHandle, "ReportLog");
        if (fnMessageProc == (MsgProc)NULL){
            dwErrorNumber = GetLastError();
            MsgBox("GetProcAddress(%ld) error.", dwErrorNumber);
        }

        fwEventType = EVENTLOG_INFORMATION_TYPE;
        dwIDEvent = MSG_STUB_START;
        wStrings = 1;
        plpszStrings[0] = SERVICE_NAME;
        dwData = 0;
        lpvData = (LPVOID)NULL;
        dwRet = (fnMessageProc)(fwEventType, dwIDEvent, wStrings, plpszStrings, dwData, lpvData);
    }

    /* "hStopEvent" がシグナル状態になるまで待機 */
    WaitForSingleObject(hStopEvent, INFINITE);

    /* サービスの停止処理 */
    if (!ReportStatus(SERVICE_STOPPED, 0, 1000)){
        MsgBox("ReportStatus(SERVICE_STOPPED) error.", "");
        return;
    }
    if (hStopEvent != NULL)
        CloseHandle(hStopEvent);
    if (SecDesc != NULL)
        LocalFree((HLOCAL)SecDesc);

    return;
}
7.4 インストーラ

 インストーラは、CreateService()かDeleteService()を発行するだけのプログラムである。 技術的な問題は何もない。 以下は、前節で説明したサービスをシステムに登録/削除するインストーラである。 サービスの起動/停止もプログラムで行うこ とはできるが、コントロールパネルのサービスメニューでできるため、あまり意味はない。

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "service.h"

SC_HANDLE hSCManager;          // サービス制御マネージャ・データベース・ハンドル
CHAR ExeFile[PATH_LENGTH];     // サービス実行ファイル名
CHAR FullFile[PATH_LENGTH];    // サービス実行ファイル名(フル・パス) 

VOID InstallService(LPCSTR);
VOID RemoveService();

VOID main()
{
    DWORD Operation = 0;    // 処理指定子
    DWORD dwErrorNumber; 

    /* サービス制御マネージャ・データベースをオープン */
    hSCManager = OpenSCManager(
                    NULL,    // ローカル・システム
                    NULL,    // デフォルトのデータベース
                    SC_MANAGER_ALL_ACCESS);    // 全アクセス・タイプを許可
    if (hSCManager == (SC_HANDLE)NULL) {
        dwErrorNumber = GetLastError();
        printf("OpenSCManager(%ld) error.\n", dwErrorNumber);
        ExitProcess(dwErrorNumber);
    }

    /* 実行する処理を指定する */
    printf("サービス %s に対する処理を入力してください(%d=登録、%d=削除): ", 
        SERVICE_NAME, ID_REGISTER, ID_DELETE);
    scanf("%d", &Operation);
    switch (Operation) {
    case ID_REGISTER:
        printf("サービスの実行ファイルを入力してください: ");
        scanf("%s", ExeFile);
        if (! SearchPath(NULL, ExeFile, ".EXE", PATH_LENGTH, FullFile, NULL))
            printf("ファイル(%s)が見つかりません。\n", ExeFile);
        else
            InstallService(FullFile);
        break;
    case ID_DELETE:
        RemoveService();
        break;
    default:
        printf("もう一度入力してください。\n");
        break;
    }

    /* データベースをクローズ */
    CloseServiceHandle(hSCManager);
}

/* サービス制御マネージャ・データベースにサービスを登録する。 */
VOID InstallService(LPCSTR ServiceExeFile)
{
    SC_HANDLE hService;    // サービス・ハンドル

    /* サービスの登録 */
    hService = CreateService(
                hSCManager,                   // データベース・ハンドル
                SERVICE_NAME,                 // サービス名
                SERVICE_NAME,                 // サービス識別名
                SERVICE_ALL_ACCESS,           // 全アクセスの許可
                SERVICE_WIN32_OWN_PROCESS,    // 独立の Win32 サービス・プロセス
                SERVICE_DEMAND_START,         // 起動要求時に開始
                SERVICE_ERROR_NORMAL,         // 起動失敗時にメッセージを表示
                ServiceExeFile,               // 実行ファイル名
                NULL,                         // ロード順序グループなし
                NULL,                         // タグは不要
                NULL,                         // 依存グループなし
                NULL,                         // LocalSystem アカウントを使用
                NULL);                        // パスワードなし

    /* 後処理 */
    if (hService == NULL)
        printf ("CreateService(%ld) error.\n", GetLastError());
    else{
        printf ("サービス %s が登録されました。\n", SERVICE_NAME);
        CloseServiceHandle(hService);
    }
}

/* サービス制御マネージャ・データベースからサービスを削除する。 */
VOID RemoveService()
{
    SC_HANDLE hService;    // サービス・ハンドル

    /* サービスのオープン */
    hService = OpenService(
                hSCManager,             // データベース・ハンドル
                SERVICE_NAME,           // サービス名
                SERVICE_ALL_ACCESS);    // 全アクセス・タイプを許可
    if (hService == NULL) {
        printf ("OpenService(%ld) error.\n", GetLastError());
        return;
    }

    /* サービスの削除 */
    if (! DeleteService(hService)) 
        printf ("DeleteService(%ld) error.\n", GetLastError());
    else
        printf ("サービス %s を削除しました。\n", SERVICE_NAME);

    /* サービス・ハンドルをクローズ */
    CloseServiceHandle(hService);
}