◆パフォーマンスモニタ returns 〜 カスタムパフォーマンスモニタの作成 その3

[ ホーム ] [ 上へ ] [ パフォーマンスモニタ returns 〜 カスタムパフォーマンスモニタの作成 その4 ]

・実際例 - 開発環境 Windows XP + Visual C++ 2005

これから実際にカスタムパフォーマンスDLLを作成しますが、その前にモニタリングする アプリケーションがないといけません。ということで前準備として次のような単純なアプリケーションを 作ってモニタ対象のプロセスとします。

ダイアログベースのアプリケーションで、表示されている時間と一定時間ごとに スタティックコントロールの背景色を変更させます。この経過時間と背景色を後で作成する パフォーマンスDLLでモニタしてみます。

<モニタ対象アプリケーションの作成>

ツールバーのファイル(F)→新規作成(N)→プロジェクト(P)で「新しいプロジェクト」 ダイアログを表示させ、以下のように設定してください。

プロジェクトの種類(P):Visual C++
テンプレート(T)      :MFCアプリケーション
プロジェクト名(N)    :Dnp
ソリューション名(M)  :PerfCustom

→OK

アプリケーションウィザードが起動するので次のように設定してください。
→次へ

アプリケーションの種類

      アプリケーションの種類:ダイアログベース(D)                    (後はデフォルト)

→次へ

ユーザーインターフェイス機能

       システムメニュー(S)のみチェック                                (後はデフォルト)

→次へ

高度な機能
(デフォルト)
→次へ
→完了

(リソースの作成)

スケルトンができていますが、最初にダイアログのリソースを作成します。 表示(V)→その他のウィンドウ(E)→リソースビュー(R)でリソースビューを表示して IDD_DNP_DIALOGのダイアログをダイアログエディタで表示して以下のように設定してください。

コントロールの種類 ID プロパティ
[1] Static Text IDC_STC_COLOR Client Edge True
[2] Static Text IDC_STC_HEX Client Edge True
[3] Static Text IDC_STC_DEC Client Edge True
[4]  Static Text IDC_STC_ELAPSED Client Edge True
[5] Button IDOK デフォルト デフォルト
[6] Button IDCANCEL デフォルト デフォルト

(実装)

最初に他プロセスから通信するための共通のヘッダを作成します。 ソリューションエクスプローラを表示して「ヘッダーファイル」フォルダを右クリックして 追加(D)→新しい項目(W)で「新しい項目の追加」ダイアログを表示して以下のように ヘッダファイルを作成してください。

カテゴリ(C)      :コード
テンプレート(T)  :ヘッダファイル(.h)
ファイル名       :DnpShare.h

ヘッダファイルが開かれているのでDnpShare.hという名前で保存して、 以下のように記述してください。

//DnpShare.h - 共有メモリ用 共通ヘッダファイル
#pragma once

#define   DNP_CAPTION    "DnpApp-"        //アプリケーションCaptionプレフィックス
#define   DNP_FILEMAP    "DnpFileMap_"    //ファイルマッピングオブジェクト名プレフィックス
#define   DNP_SEMA       "DnpSema_"       //共有メモリへの同期オブジェクト名プレフィックス

//共有メモリ
typedef struct _SHARE_MEM
{
    time_t      tmStart;                  //ダイアログ開始時刻
    COLORREF    rgbColor;                 //IDC_STC_COLORスタティックコントロール表示色
} SHARE_MEM, *LPSHARE_MEM;

DNP_CAPTIONはDnpダイアログのウィンドウを検索する際、ウィンドウのキャプションで 走査する仕様なので共通のヘッダに定義しています。SHARE_MEM構造体は他プロセスとの通信用の 共有メモリで、パフォーマンスDLLがアクセスするパフォーマンスデータをメンバとして保持しています。 DNP_FILEMAPとDNP_SEMAについてはプロセス間通信の分野なので説明は割愛します。

次に他プロセスとの通信を管理するCShareクラスを作成します。
ソリューションエクスプローラを右クリックして追加(D)→クラス(C)で「クラスの追加」 ダイアログボックスを表示させ次のように設定してください。

カテゴリ(C)    :C++
テンプレート(T):C++クラス              →追加(A)

汎用C++クラスウィザードが起動するので次のクラスを作成してください。

クラス名(L)    :CShare                 →完了

ヘッダファイル/実装ファイルに次のように記述してください。

//共有メモリ管理クラスヘッダファイル - Share.h
#pragma once

#include "DnpShare.h"      //共有メモリ用ヘッダ
#include  <afxmt.h>        //同期オブジェクトヘッダ

class CShare
{
//メンバ変数/////////////////////////////////////////////
protected:
    HANDLE          m_hMapFile;      //ファイルマッピングオブジェクトハンドル
    SHARE_MEM       *m_pShare;       //共有メモリへのポインタ
    CSemaphore      *m_pSema;        //同期オブジェクトへのポインタ

//メンバ関数/////////////////////////////////////////////
private:
    CShare(const CShare &obj);             //コピーコンストラクタ

public:
    CShare();                              //コンストラクタ
    virtual ~CShare();                     //デストラクタ

    //共有メモリ初期化
    int Create();
    //共有メモリ終了処理
    void Terminate();
    //共有メモリへ書込み
    BOOL Write(LPCVOID pData,               //書込みデータへのポインタ
               DWORD dwSize,                //pDataデータのサイズ
               DWORD dwOffset = 0,          //書き込み位置
                                            //(共有メモリの先頭からのバイト数単位のOffset)
               DWORD dwTimeout = INFINITE); //共有リソースアクセス権獲得までの時間

    //共有メモリの読み込み
    BOOL Read(OUT    void *pData,                       //取得データへのポインタ
              IN OUT DWORD &dwSize,                     //pDataのサイズ
              IN     DWORD dwTimeout = INFINITE) const; //読込みまでのタイムアウト
};
//共有メモリ管理クラス実装ファイル - Share.cpp
#include "StdAfx.h"
#include "Share.h"

//コンストラクタ(private)
CShare::CShare() : m_hMapFile(NULL)
                 , m_pShare(NULL)
                 , m_pSema(NULL)
{
}

//コピーコンストラクタ(private)
CShare::CShare(const CShare &obj) : m_hMapFile(NULL)
                                      , m_pShare(NULL)
                                      , m_pSema(NULL)
{
}

//デストラクタ
CShare::~CShare()
{
    Terminate();
}

//共有メモリ作成
//戻値 1:初期化成功 0:初期化済 -1:失敗
int CShare::Create()
{
    if (m_hMapFile && m_pShare && m_pSema)
        return 0;

    if (m_hMapFile || m_pShare || m_pSema)
        return -1;

    //セマフォ名/ファイルマッピングオブジェクト名作成
    DWORD          dwProcessId = ::GetCurrentProcessId();
    CString        strProcessId;
    CString        strSema(_T(DNP_SEMA));
    CString        strMapFile(_T(DNP_FILEMAP));

    strProcessId.Format(_T("%d"), dwProcessId);
    strSema += strProcessId;
    strMapFile += strProcessId;

    //セマフォ構築
    m_pSema = new CSemaphore(1, 1, strSema, NULL);

    //ファイルマッピングオブジェクト作成
    m_hMapFile = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                     NULL,
                                     PAGE_READWRITE,
                                     0,
                                     sizeof(SHARE_MEM),
                                     strMapFile );
    if (! m_hMapFile)
    {
        delete m_pSema;           m_pSema = NULL;
        return -1;
    }

    //ビューのマッピング
    m_pShare = (LPSHARE_MEM)::MapViewOfFile(m_hMapFile, FILE_MAP_WRITE, 0, 0, 0);

    if (! m_pShare)
    {
        delete m_pSema;               m_pSema = NULL;
        ::CloseHandle(m_hMapFile);    m_hMapFile = 0;
        return -1;
    }

    //共有メモリで初期化
    ::memset(m_pShare, 0, sizeof(SHARE_MEM));

    return 1;
}

//共有メモリ終了処理
void CShare::Terminate()
{
    if (m_pShare)
    {
        ::memset(m_pShare, 0, sizeof(SHARE_MEM));     //共有メモリをでリセット
        ::UnmapViewOfFile(m_pShare);     m_pShare = NULL;
    }

    if (m_hMapFile) {::CloseHandle(m_hMapFile);        m_hMapFile = 0;}
    if (m_pSema)    {delete m_pSema;                   m_pSema = NULL;}
}

//共有メモリへの書込み
//引数 pData 書込みデータへのポインタ
//     dwSize pDataのデータサイズ
//     dwOffset 書き込み開始位置(共有メモリの先頭位置からのOffset)
//     dwTimeout 共有リソースアクセス権獲得までの時間
//戻値 書込み成功 TRUE 失敗 FALSE
BOOL CShare::Write(LPCVOID pData,
                   DWORD dwSize,
                   DWORD dwOffset /*= 0*/,
                   DWORD dwTimeout /*=INFINITE*/ )
{
    CSingleLock slock(m_pSema, FALSE);

    //書き込みサイズが共有メモリのサイズより大きいとき
    if (dwSize > sizeof(SHARE_MEM) - dwOffset)
        return FALSE;

    //書き込み位置が共有メモリサイズより大きいとき
    if (dwOffset > sizeof(SHARE_MEM))
        return FALSE;

    //初期化できていないとき
    if (!m_hMapFile || !m_pShare || !m_pSema)
        return FALSE;

    //セマフォ獲得
    slock.Lock(dwTimeout);

    return ! ::memcpy_s((PBYTE)m_pShare + dwOffset,
                         sizeof(SHARE_MEM) - dwOffset,
                         pData,
                         dwSize);
}

//共有メモリの読み込み
//引数 pData 取得データへのポインタ
//     dwSize pDataのサイズ
//     dwTimeout 読込みまでのタイムアウト
//戻値 読込成功 TRUE 読込失敗 FALSE
BOOL CShare::Read(OUT void *pData,
                  IN OUT DWORD &dwSize,
                  IN DWORD dwTimeout /*=INFINITE*/) const
{
    CSingleLock slock(m_pSema, FALSE);

    if (dwSize < sizeof(SHARE_MEM))
        return FALSE;

    //セマフォ獲得
    slock.Lock(dwTimeout);

    errno_t err = ::memcpy_s(pData, dwSize, m_pShare, sizeof(SHARE_MEM));
    if (! err) //成功
        dwSize = sizeof(SHARE_MEM);

    return !err;
}

他プロセスとの通信用の共有メモリへの書込み読込みを 管理するクラスですが、ここも本題ではないので解説は割愛します。

最後にスケルトンで作成されたダイアログクラスに

  1. CShareクラスを組み込んで共有メモリへの書込み機能追加
  2. スタティックテキストへの背景色の表示機能
  3. ダイアログ表示時間表示機能

を実装します。
DnpDlg.h、DnpDlg.cppに次のように記述してください。

// DnpDlg.h : ヘッダーファイル
//
#pragma once

#include "Share.h"         //共有メモリ管理クラス

// CDnpDlg ダイアログ
class CDnpDlg : public CDialog
{
// コンストラクション
public:
    CDnpDlg(CWnd* pParent = NULL); // 標準コンストラクタ
// ダイアログデータ
    enum { IDD = IDD_DNP_DIALOG };

protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート

// 実装
protected:
    HICON m_hIcon;

// 生成された、メッセージ割り当て関数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();

// メンバ変数/////////////////////////////////////////////////////////////
protected:
    UINT_PTR         m_nIDElapsed;     //経過時間タイマID
    UINT_PTR         m_nIDColor;       //色変更タイマID
    time_t           m_tmStart;        //ダイアログ表示開始時刻
    CBrush           m_brush;          //色表示用ブラシ
    CShare           m_Share;          //共有メモリ管理クラス

//メンバ関数/////////////////////////////////////////////////////////////
protected:
    virtual void OnOK();

public:
    afx_msg void OnTimer(UINT_PTR nIDEvent);
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
    afx_msg void OnDestroy();

    DECLARE_MESSAGE_MAP()
};
// DnpDlg.cpp : 実装ファイル
//
#include "stdafx.h"
#include "Dnp.h"
#include "DnpDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define     MIN_COLOR       0                  //RGB色要素最小値
#define     MAX_COLOR       255                //RGB色要素最大値
#define     INIT_COLOR      RGB(150, 150, 150) //初期表示色

// CDnpDlg ダイアログ
CDnpDlg::CDnpDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CDnpDlg::IDD, pParent)
    , m_nIDElapsed(1)
    , m_nIDColor(2)
    , m_brush(INIT_COLOR)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    ::srand( (UINT)::time(NULL) ); //乱数シード値初期化
}

void CDnpDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CDnpDlg, CDialog)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
    ON_WM_TIMER()
    ON_WM_CTLCOLOR()
    ON_WM_DESTROY()
END_MESSAGE_MAP()

// CDnpDlg メッセージハンドラ
BOOL CDnpDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    SetIcon(m_hIcon, TRUE);  // 大きいアイコンの設定
    SetIcon(m_hIcon, FALSE); // 小さいアイコンの設定

    //開始時間セット
    ::time(&m_tmStart);

    //タイマセット
    SetTimer(m_nIDElapsed, 1000, NULL);
    SetTimer(m_nIDColor,3000, NULL);

    //共有メモリ初期化
    if ( m_Share.Create() < 0)
        ::AfxMessageBox(_T("共有メモリ初期化失敗!"));

    //共有メモリにダイアログ開始時刻書込み------------------------------------
    if (! m_Share.Write( (LPCVOID)&m_tmStart, sizeof(time_t), 0, 1000) )
        ::AfxMessageBox(_T("ダイアログ開始時刻の共有メモリへの書込み失敗!"));
    //------------------------------------------------------------------------

    //経過時間表示初期化
    CString         strColor;

    GetDlgItem(IDC_STC_ELAPSED)->SetWindowTextW(_T("0"));

    strColor.Format(_T("%06X"), INIT_COLOR);
    GetDlgItem(IDC_STC_HEX)->SetWindowTextW(strColor);

    strColor.Format(_T("%d"), INIT_COLOR);
    GetDlgItem(IDC_STC_DEC)->SetWindowTextW(strColor);

    //ダイアログ表題設定-------------------------------------------------------
    CString         strProcessId;
    CString         strCaption(_T(DNP_CAPTION));

    strProcessId.Format( _T("%d"), ::GetCurrentProcessId() );
    strCaption += strProcessId;
    
    SetWindowText(strCaption);
    //-------------------------------------------------------------------------

    return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

void CDnpDlg::OnPaint()
{
    //・
    //・
}

HCURSOR CDnpDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

void CDnpDlg::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == m_nIDElapsed)           //経過時間タイマ
    {
        time_t         tmCurrent;
        CString        strElapsed;

        ::time(&tmCurrent);         //現在時間の取得

        //ダイアログ表示からの経過時間の文字列化
        strElapsed.Format(_T("%d"), static_cast<DWORD>(::difftime(tmCurrent, m_tmStart)) );
        GetDlgItem(IDC_STC_ELAPSED)->SetWindowTextW(strElapsed);

    } else if (nIDEvent == m_nIDColor)        //色変更タイマ
    {
        BYTE           color[3];
        CString        strColor;
        COLORREF       rgb;

        //表示色のRGB各要素生成
        for (int i=0; i<sizeof color/sizeof color[0]; i++)
            color[i]= (BYTE)(((double)::rand() / (double)RAND_MAX) * MAX_COLOR + MIN_COLOR);

        rgb = RGB(color[0], color[1], color[2]);

        //共有メモリに色の書込み--------------------------------------------------
        m_Share.Write( (LPCVOID)&rgb, sizeof(COLORREF), sizeof(time_t), 500);
        //------------------------------------------------------------------------

        //色表示用ブラシ作成
        m_brush.DeleteObject();
        m_brush.CreateSolidBrush(rgb);

        //表示色進数表示
        strColor.Format(_T("%06X"), rgb);
        GetDlgItem(IDC_STC_HEX)->SetWindowTextW(strColor);

        //表示色進数表示
        strColor.Format(_T("%d"), rgb);
        GetDlgItem(IDC_STC_DEC)->SetWindowTextW(strColor);

        //再描画
        Invalidate();
    }
}

//コントロール背景色描画
HBRUSH CDnpDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    if (pWnd->GetDlgCtrlID() == IDC_STC_COLOR && nCtlColor == CTLCOLOR_STATIC)
    {
        hbr = (HBRUSH)m_brush;
    }
    return hbr;
}

void CDnpDlg::OnDestroy()
{
    CDialog::OnDestroy();

    //タイマ解除
    KillTimer(m_nIDElapsed);
    KillTimer(m_nIDColor);
}

void CDnpDlg::OnOK()
{
    SHARE_MEM     share_mem;
    DWORD         dwSize = sizeof(SHARE_MEM);
    time_t        tmCurrent;
    double        dblElapse;
    CString       strTime;
    CString       strColor;

    //共有メモリ受取りデータ初期化
    ::memset(&share_mem, 0, sizeof(SHARE_MEM));

    //共有メモリ読込み
    if (! m_Share.Read( (void *)&share_mem, dwSize, 1000) )
        ::AfxMessageBox(_T("共有メモリからデータを読み取れませんでした!"));

    ::time(&tmCurrent);     //現在時刻
    dblElapse = ::difftime(tmCurrent, share_mem.tmStart);

    strTime.Format(_T("経過時間- %d"), (DWORD)dblElapse);
    strColor.Format(_T("背景色 - %d"), share_mem.rgbColor);

    ::AfxMessageBox(strTime + _T("\n") + strColor, MB_ICONINFORMATION);

    //CDialog::OnOK();
}

以上でモニタ対象アプリケーションができたので、ビルドしてダイアログを 表示させてみてください。3秒おきにスタティックテキストの背景色が変化し、1秒おきに 表示時間が変化するのを確認してください。

また、OKボタンをクリックして背景色と表示時間が正しくメッセージボックスに 表示されるかも確認してください。ここに表示される値はダイアログ内からデータを取得してなくて、 CDnpDlg::OnTimer、CDnpDlg::OnOKをみれば分かりますが、ダイアログから書き込んだ共有メモリの 情報を表示しているので、これが正しく表示されていれば、他プロセスからのアクセス (今回の場合はパフォーマンスDLL)で、データは正しく取得できることが確認できます。

続いて次の前準備であるイベントログの実装を行います。