アクティブプロセス測定 その5

インターフェイス部分。ウインドウはいらないのでただ常駐すればいい、でもコンソールにしたらDOS窓が出てきてうざいので、最初は窓なしWindowsアプリにしたんだけど、ウインドウメッセージなりシグナル(?)なりのハンドラーを作りたかったので、SW_HIDEでWindowを生成することにしました。

2重起動防止

2重で起動してても意味がないので、防止するにはどうしたらいいのか。ロックファイルを作る、プロセス一覧から同じ名前のプロセスを探す、とか色々考えたけど、調べてみるとMutexを使うのが一番簡単そう。

// check already-running
hMutex = CreateMutex(FALSE, 0, "MUTEX_GETAP_CHECK");
if(GetLastError() == ERROR_ALREADY_EXISTS){
     ReleaseMutex(hMutex);
     CloseHandle(hMutex);
     return FALSE;
}

これだけ。

シャットダウンのときとかにログを出しておきたい

I/Oの負担になるので、デフォルトでは10分おきとかに出力させてるんだけど、シャットダウンのときとか終了時にはログを出しておきたい*1。まあ、10分くらいならあきらめるってのもアリだけどね・・・。コンソールアプリだとSetConsoleCtrlHandler()でコントロールイベント(っていうのか?)をトラップできるらしい。でもこれはWindowsアプリなので、何も表示させないウインドウを作って、そこにメッセージを送ることにします。
シャットダウンやリブートのときにはWM_QUERYENDSESSIONが全てのウインドウに送られるらしいので、それをキャッチしてログを出させる。

LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
     switch(message){
     case WM_DESTROY:
          PostQuitMessage(0);
          return 0; 
//    case WM_POWERBROADCAST:
     case WM_QUERYENDSESSION:
          ap.fileoutput();
          return TRUE;
     }
     return DefWindowProc(hwnd,message,wparam,lparam);
}

ソースコードはこんなかんじ。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#include "ActiveProcInfo.h"

#define PLEN 256
#define UPDATE_TIME 1000 // 1 second
#define LOGGING_TIME 10*60*UPDATE_TIME

HWND CreateMinimalWindow(void);
LRESULT CALLBACK WindowProc(HWND,UINT,WPARAM,LPARAM);
VOID CALLBACK callback1(HWND, UINT, UINT, DWORD);
VOID CALLBACK callback2(HWND, UINT, UINT, DWORD);
ActiveProcInfo ap;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR lpCmdLine, int nCmdShow){
     MSG msg;
     HWND hwnd;
     time_t timer;
     struct tm *t_st;
     int logging_time;
     char path[PLEN], drive[PLEN], dir[PLEN], file[PLEN], file2[PLEN], ext[PLEN];
     HANDLE hMutex;

     // check already-running
     hMutex = CreateMutex(FALSE, 0, "MUTEX_GETAP_CHECK");
     if(GetLastError() == ERROR_ALREADY_EXISTS){
          ReleaseMutex(hMutex);
          CloseHandle(hMutex);
          return FALSE;
     }
     // set logging-time interval
     logging_time = atoi(lpCmdLine) * UPDATE_TIME;
     if(logging_time < UPDATE_TIME){ logging_time = LOGGING_TIME; }
     // set logfile name
     time(&timer);
     t_st = localtime(&timer);
     GetModuleFileName(NULL, path, sizeof(path)/sizeof(char));
     _splitpath(path, drive, dir, file, ext);
     sprintf(file2, "%s_%04d%02d%02d%02d%02d%02d", file, t_st->tm_year+1900,
             t_st->tm_mon+1, t_st->tm_mday, t_st->tm_hour, t_st->tm_min, t_st->tm_sec);
     _makepath(path, drive, dir, file2, ".log");
     ap.setfilename(path);
     // set timer
     SetTimer(NULL, 1, UPDATE_TIME, callback1);
     SetTimer(NULL, 2, logging_time, callback2);
     // open hidden window
     if((hwnd=CreateMinimalWindow()) == NULL) return  0;
     ShowWindow(hwnd, SW_HIDE);
     // looping...
     while(GetMessage(&msg, NULL, 0, 0)){
          TranslateMessage(&msg);
          DispatchMessage(&msg);
     }
     return msg.wParam;
}

VOID CALLBACK callback1(HWND hwnd, UINT nMsg, UINT idEvent, DWORD dwTime)
{
     ap.update();
}
VOID CALLBACK callback2(HWND hwnd, UINT nMsg, UINT idEvent, DWORD dwTime)
{
     ap.fileoutput();
}
#define WCLASSNAME "getapMinimalWindow"
HWND CreateMinimalWindow(void)
{
     WNDCLASS wc;
     wc.style         = CS_HREDRAW | CS_VREDRAW;
     wc.lpfnWndProc   = WindowProc;
     wc.cbClsExtra    = 0;
     wc.cbWndExtra    = 0;
     wc.hInstance     = NULL;
     wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
     wc.hCursor       = LoadCursor(NULL,IDC_ARROW);
     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
     wc.lpszMenuName  = NULL;
     wc.lpszClassName = WCLASSNAME;
     
     if(RegisterClass(&wc) == 0) return NULL;
     return CreateWindow(WCLASSNAME, "",WS_OVERLAPPEDWINDOW,0,0,0,0,NULL,NULL,NULL,0);
}
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
     switch(message){
     case WM_DESTROY:
          PostQuitMessage(0);
          return 0; 
//    case WM_POWERBROADCAST:
     case WM_QUERYENDSESSION:
          ap.fileoutput();
          return TRUE;
     }
     return DefWindowProc(hwnd,message,wparam,lparam);
}

ソースは短いんだけど、Windows APIを駆使して作ると何だかWindowsハックしてるような気持ちになりますね。

まあ何にしても、目的のものができてよかった。案外フリーウェアでこういうのがないんだよなあ・・・。

*1:ちなみに、デストラクタでやればいーじゃん!って最初は思ったけどうまく動いてくれないのね・・・