ログファイルの出力をのぞき見する方法

戻る

アプリケーションのデバッグを行うときや,ユーザインタフェースがない アプリケーションが実行結果を出力する,ファイルの内容を参照する場合,メモ帳など のエディタを利用します.しかし,ファイルをオープンしてしまうと,それ以降の 出力結果を見る事ができません.UNIXでは,tailコマンドを利用して,コンソールに 出力結果を表示させることができますが,Windowsではそのようなコマンドはありま せんし,コンソールに出力させながらファイルに出力するにはteeコマンドが必要に なります.そこで,エディットコントロールを利用したファイル監視ツールを作成して みることにします.

21-1 ファイルを覗く

他のアプリケーションが出力しているファイルを参照する場合,先にファイルを 開いているアプリケーションが排他ロックを行っていないことが前提になります. 排他制御は,複数のアプリケーションがお互いに書き込みを行っているときには, 必ず行う必要がありますが,一方が書き込みのみでもう一方が読み込みしか行わない 場合は必ずしも行う必要はありません.
通常,書き込みが行われているファイルを読み込む処理が必要な場合,出力する側が ある単位,たとえばレコード単位で出力を行います.ソケットで通信を行う アプリケーションでは考慮する必要がありますが,レコードが途中で切れている可能性 があるときの処理は意外と大変です.
ここまでは,アプリケーションの都合で制御することができます.しかし,ファイル はブロック単位でディスクに書き込みを行います.このため,バッファが溢れるまで 実際に書き込みを行いません.しかし,これではリアルタイムにログを表示することが できません.

21-2 ファイルへの出力をすぐに反映させるには

ファイルへの出力をすぐに反映させる一番簡単な方法は,fclose や Win32 API CloseHandle でファイルを閉じてしまうことです.しかし,頻繁に出力を 行う場合は処理速度の低下を覚悟する必要があります.そこで,ファイルを閉じずに データを実際に書き込ませるAPIが用意されています.Cランタイムのfopen で ファイルをオープンしている場合は,fflush, Win32 API CreateFile でファイルを オープンしている場合は,Win32 API FlushFileBuffers を利用します.
当然のことですが,これらの関数は「書き込み」モードでオープンしているファイル ハンドルでなければ動作しません.
WindowsNT では,遅延書き込み機能によってディスクアクセスが高速化されますが, 実際の書き込みはシステムのCPUが空いているときに行われます.このため, 意図的にバッファをフラッシュさせる必要があります.ただし,データを書き込む度に バッファのフラッシュを行うとファイルのアクセス速度はかなり低下しますので, 必要な場合を除いて行わないほうがよいでしょう.

表21-1:Win32 API FlushFileBuffers
BOOL FlushFileBuffers(HANDLE hFile)
バッファをクリアしてバッファ内のデータをファイルに書き込む

引数
HANDLE hFile ... GENERIC_WRITEアクセスでオープンしたファイル

戻り値
正常終了 TRUE
異常終了 FALSE

21-3 64ビット整数

Windows95/WindowsNTは今のところ32ビットOSであるため,CPUが64ビット の演算機能を持っていてもOSとしては,直接利用するコードはありません. しかし,ファイルサイズなど32ビットでは表現できなくなる恐れのある値は, 64ビットで取得する機能を提供しています.
Visual C++ は __int64型を提供しており,内部でどのようなコードに展開されるか を意識しなくても64ビットの数値を扱うことができるようになっています. Win32 では直接32ビット値より大きい値を操作することができません.このため, Win32 API GetFileSize や Win32 API SetFilePointer などは,32ビットの引数と 戻り値で最大64ビットの値を操作することができるようになっています.
リスト21-1:Win32 の64ビット型(WINNT.H)

○Visual C++ の整数型
__int8char
__int16short
__int32int
__int64相当するANSI型はない
typedef __int64 LONGLONG; typedef unsigned __int64 DWORDLONG; typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; struct { DWORD LowPart; LONG HighPart; } u; LONGLONG QuadPart; } LARGE_INTEGER; typedef LARGE_INTEGER *PLARGE_INTEGER; typedef union _ULARGE_INTEGER { struct { DWORD LowPart; DWORD HighPart; }; struct { DWORD LowPart; DWORD HighPart; } u; DWORDLONG QuadPart; } ULARGE_INTEGER; typedef ULARGE_INTEGER *PULARGE_INTEGER;

21-4 アプリケーションにファイル名を渡すには

DOSやUNIXでは,一般的にファイル名などのパラメータをアプリケーションに 設定するときにはコマンドラインで指定します.しかし,Windowsアプリケーションでは コンソールアプリケーションでなければ,コマンドラインから実行することはあまり ありません.ウィンドウを開く一般的なWindowsアプリケーションでは,実行ファイル 名やショートカットをダブルクリックして実行させます.このようなアプリケーション にファイルを教えるには,次のような方法が考えられます.
  • 「ファイル」-「開く」で選択する
  • ウィンドウにドラッグアンドドロップを行う
  • ショートカットまたはアプリケーションのファイル名にドラッグ&ドロップを行う
    Windows95やWindowsNT4.0以降では,デスクトップ上にショートカットを作成する ことができます.以前のWindowsでは,ファイルマネージャでEXEファイルに直接 入力ファイルをドラッグ&ドロップして起動させることができましたが, ショートカットでも同じように行うことができます.
    なお,ここでのドラッグ&ドロップは,OLEオブジェクトに対するもの (IDropSource, IDropTargetインターフェース)でなく,単純にファイル名を引き渡す ための機能を指します.

    ●「ファイルを開く」ダイアログボックスでファイルを指定する
    「ファイルを開く」ダイアログボックスを表示させるには, Win32 API GetOpenFileName を利用します.このAPIは OPENFILENAME構造体に設定を 行う必要があります.なお,「ファイルを保存」ダイアログボックスを表示させるには Win32 API GetSaveFileName を利用します.

    ●ウィンドウ上にドラッグ&ドロップを行ってファイルを指定する
    ウィンドウ上にファイル名をドラッグ&ドロップを行うことができるものが多く あります.このような機能を持つアプリケーションは,Win32 API CreateWindowEx で ウィンドウを生成しています.
    このAPIは,Win32 API CreateWindow に拡張スタイル が指定できるようになっているだけですので,拡張されたスタイルを指定しないとき には使うことはありませんが,CreateWindowEx で指定可能な拡張スタイルを知って おくと,何かの役に立つかもしれません.
    ウィンドウにドラッグ&ドロップを受け入れる設定を行うことによりファイル名の 入口はできました.しかし,実際にファイル名を取得する処理を行わなければ,実際に ファイル名を得ることができません.ドラッグ&ドロップを許可したウィンドウには, WM_DROPFILES メッセージが送信されます.
    このメッセージは,ドロップされたファイルの受け手として登録したアプリケーション のウィンドウの上にドラッグしたファイルをドロップしたときに送られます. ドラッグするファイルは,1つでも複数でもよく,複数選択した場合は Win32 API DragQueryFile でファイルの総数を取得します.
    そして,ファイル総数回 DragQueryFile を呼び出してファイル名を取得します.

    表18-1:Win32 API GetOpenFileName/GetSaveFileName
    BOOL GetOpenFileName(LPOPENFILENAME lpofn)
    オープンするファイルを選択するシステム定義のダイアログボックスを表示

    引数
    LPOPENFILENAME lpofn ... 初期化データ

    戻り値
    正常終了 TRUE
    異常終了 FALSE
    BOOL GetSaveFileName(LPOPENFILENAME lpofn)
    保存するファイルを選択するシステム定義のダイアログボックスを表示

    引数
    LPOPENFILENAME lpofn ... 初期化データ

    戻り値
    正常終了 TRUE
    異常終了 FALSE

    表21-3:Win32 API CreateWindowEx の拡張パラメータ
    WS_EX_ACCEPTFILES ドラッグドロップされたファイルを受け入れるWS_EX_DLGMODALFRAME 二重の境界を持つウィンドウを作成する.dwStyleパラメータにWS_CAPTIONスタイル フラグを指定することにより任意にこのウィンドウをタイトルバー付きで作成する ことがでる.
    WS_EX_NOPARENTNOTIFY このスタイル付きで作成された子ウィンドウが作成されたり 破棄されたりするときにその親ウィンドウにWM_PARENTNOTIFY メッセージを送らない.
    WS_EX_TOPMOST ほかのすべての最前面でないウィンドウの上に位置しウィンドウが非アクティブ化 状態のときにも最前面に表示される.Win32 API SetWindowPos でも制御できる.
    WS_EX_TRANSPARENT 透過になることを指定する.このウィンドウの下にあるウィンドウは遮られる ことなく表示される.

    ●ショートカットにドラッグ&ドロップを行ってファイルを指定する
    ショートカットには,実行ファイルと実行パラメータやディレクトリを設定する こともできますが,デスクトップ上に置かれたショートカットにファイルをドラッグ& ドロップすることで,ドロップしたファイル名をアプリケーションに通知することが できます.ただし,ウィンドウへ行う場合と異なり1度に1ファイルしか通知すること ができません.

    ●どこでファイル名を知るのか
    ショットカットにドロップされたファイル名は,コマンドラインパラメータに設定 されます.Windowsアプリケーションではあまり利用することはありませんが, コマンドラインパラメータを取得することができます.
    コマンドラインパラメータは,Win32 API WinMain のlpszCmdLine に設定されます. main 関数では,argc, argv でパラメータの個数と文字列の先頭を指すポインタの リストを得ることができますが,WinMain では単純な文字列として 引き渡されます.
    <WinMainの引数>
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
    つまり,ショートカットにドロップされたファイル名を認識できるアプリケーション は,コマンドプロンプトでファイル名を指定しても正しく実行できることを意味 します.コマンドラインパラメータは,Win32 API GetCommandLine でも得ることが できます.この場合,パラメータの前に実行ファイルのフルパス名が 設定されます.
    c:\bin\appname.exe に c:\diec\fikename.txt を指定して実行すると,それぞれ 次のような文字列が設定されます.
    lpszCmdLine ... c:\diec\fikename.txt
    Win32 API GetCommandLine ... "c:\bin\appname.exe" c:\diec\fikename.txt

    リスト21-2:WM_DROPFILESの処理例
    
        HANDLE  hDrop = (HDROP)wParam;
        DWORD   dwCt, dwDropped;
        char    szFileName[MAX_PATH];
    
        /* ドロップされたファイルの数を取得する */
        dwDropped = DragQueryFile(hDrop, (UINT)-1, NULL, 0);
    
        for(dwCt = 0; dwCt < dwDropped; dwCt++){
            DragQueryFile(hDrop, dwCt, szFileName, sizeof(szFileName));
                    :
                    :
        }
        DragFinish(hDrop);
    

    表21-4:ドラッグ&ドロップAPI
    WM_DROPFILES
    hDrop = (HANDLE)wParam;
    ドロップ構造体のハンドル
    このハンドルは,Win32 API DragQueryFile,
    Win32 API DragQueryPoint, Win32 API DragFinish
    で利用します.
    VOID DragAcceptFiles(HWND hWnd, BOOL fAccept)
    ウィンドウがドロップされたファイルを受け入れるかどうかを登録

    引数
    HWND hWnd ... ウィンドウハンドル
    BOOL fAccept ... 受け入れる:TRUE/受け入れない:FALSE

    戻り値
    なし
    UINT DragQueryFile(HDROP hDrop, UNIT uFile, LPTSTR lpszFile, UINT uBuffSize)
    ドロップされたファイルのファイル名を取得する

    引数
    HDROP hDrop ... ドロップ構造体のハンドル
    UINT uFile ... (UINT)-1 の場合ファイルの総数を返す
     0以上の場合は,その値に対応するファイル名をlpszFileが指す領域にコピーする
    LPTSTR lpszFile ... ファイル名を受け取る領域
    UINT uBuffSize ... lpszFileが指す領域のサイズ

    戻り値
    uFile == (UINT)-1の場合 ... ドロップされたファイル数
    uFile >= 0 の場合 ... 文字列の長さ('\0'を含まない)
    BOOL DragQueryPoint(HDROP hDrop, LPPOINT lppt)
    ファイルがドロップされたときのマウスポインタの位置を取得する

    引数
    HDROP hDrop ... ドロップ構造体のハンドル
    LPPOINT lppt ... マウスポインタの座標

    戻り値
    正常終了 TRUE
    異常終了 FALSE
    VOID DragFinish(HANDLE hDrop)
    ファイル名を転送するために割り当てたメモリを解放する

    引数
    HDROP hDrop ... ドロップ構造体のハンドル

    戻り値
    なし

    21-5 並列に実行するのはスレッドだけでない

    ●ウィンドウメッセージ
    Win32 では,スレッドを利用することにより複数の処理を並列に実行することが できます.しかし,ウィンドウを表示するための処理はマルチスレッドをサポート している・していないに関わらず「イベント駆動型」の処理を行う必要があります. 16ビットアプリケーションは,非同期のイベントを受け取るための手段として, ウィンドウを利用するがよくあります.
    例えば,ソケットを利用する場合,32ビットアプリケーションではスレッドを利用 して実現する機能も WSAxxxx関数 でウィンドウメッセージを利用して非同期イベント を処理します.このため,ウィンドウを表示しないソケットの処理を行うためだけの ウィンドウを生成することもよくあります.
    スレッドを利用すると,スレッド間の同期やスレッドのシャットダウン制御など, いろいろ考えなければならないことが多くあるため,ウィンドウを開く アプリケーションでは,ウィンドウメッセージで処理する方がかえって処理が楽になる こともあります.また,ソースを16ビットアプリケーションに流用する必要がある 場合,Win32 に特化した処理にすると,流用できないコードになることもありますので 用途によってプログラムの構造を十分検討してからプログラミングを行う必要が あります.

    ●定期的に処理を行うには
    ウィンドウで処理を行うには,イベントを発生させる必要あがあります.
    定期的にイベントを発生させる方法としては,Win32 API timeSetEvent などの マルティメディアタイマーを利用することも考えられますが,もっと簡単にイベント を発生させるには,Win32 API SetTimer でウィンドウにWM_TIMERメッセージを送信 することが考えられます.SetTimer は Win16 でもサポートされていまので, 16ビットでも32ビットでも動作させる必要のある処理に利用することが できます.
    SetTimer で開始したタイマーは,必ず Win32 API KillTimer で削除しなければ なりません.

    表21-5:Win32 API SetTimer/KillTimer
    UINT SetTimer(HWND hWnd, UINT uTimerId, UINT uTimeout, TIMERPROC tmproc)
    ファイル名を転送するために割り当てたメモリを解放する

    引数
    HWND hWnd ... WM_TIMERを送信するウィンドウ
    UINT uTimerId ... 0以外のタイマIDを指定(hWndがNULLのときは無視される)
    UINT uTimerOut ... タイムアウト時間(ミリ秒)
    TIMERPROC tmproc ... タイムアウト時間が経過したときに通知する関数を指す ポインタ

    戻り値
    新しいタイマを識別する整数を返す
    BOOL KillTimer(HWND hWnd, UINT uEventId)
    ファイル名を転送するために割り当てたメモリを解放する

    引数
    HWND hWnd ... WM_TIMERを送信するウィンドウ
    UINT uEventId ... Win32 API SetTimerから返却されたID

    戻り値
    正常終了 TRUE
    異常終了 FALSE

    21-6 プログラムについて

    ●プログラムの概要
    ファイルを監視して,追加された部分を表示するツールです. ファイルの選択は,「ファイル」-「開く」,ドラッグ&ドロップをサポート しています.ファイルを開くと,ウィンドウタイトルにファイル名が表示 されます.
    試しにDIRコマンドの結果を表示してみることにしましょう.
    (1) コマンドプロンプトを開きます
    (2) C:\TEMPに移動します
    (3) C:\TEMP\A.TXTを作成します
    (4) WINTAILを起動して,C:\TEMP\A.TXTを開きます
    (5) DIR >> A.TXT を実行します
    WINTAIL.EXEのウィンドウに,DIRの出力結果が表示されたと思います. (5)を繰り返すと次々に出力されます.
    もし,ここで DIR > A.TXT を実行してもログは追加されません. これは,A.TXTのファイルサイズが変わらないためです.
    それではA.TXTの内容をどのように「覗き見」したかを説明します.
    (1) 指定されたファイルを3秒ごとに監視して,ファイルサイズを調べます.
    (2) ファイルサイズが以前より増加していれば増加した部分を読み込みます.
    (3) 読み込んだ部分の終端が[CR][LF]であれば,読み込んだデータをウィンドウに表示します.
    ファイルサイズのみで追加された文字列を判断しますので,ファイルの途中に文字列 を挿入すると,以前に表示された文字列が再び表示されたり,追加した文字列以外の 文字列が表示されます.なお,ウィンドウに表示されるログが,28KBを超えると, 最初に出力された行から削除されます.

    例21-1:行の表示方法
                  :
                  :
    XXXXXXXXXXXXXXXXXXXXXXX[CR][LF]←以前の終端
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAA[CR][LF]   ←出力される行
    終端
    
                  :
                  :
    XXXXXXXXXXXXXXXXXXXXXXX[CR][LF]
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAA[CR][LF]←以前の終端
    BBBBBBBBBBBBBBBBBBBBBBBBBBBBB[CR][LF]←この行はファイルの終端の行が
    BBBBBBBBBBBBBBBBB←終端        [CR][LF]で終わるまで表示されません.
    

    ●ソースファイル
    WINTAIL.EXE (ログ監視ツール)
     WINTAIL.C (ソースファイル)
     WINTAIL.H (ヘッダファイル)
     WINTAIL.RC (リソースファイル)
     WINTAIL.ICO (アイコンファイル)
     MAKEFILE (メイクファイル)