◆プロセス間通信 - VC++ 2005 MFC その3

[ ホーム ] [ 上へ ]

<クライアントプログラム作成>

前頁のIpcSrvプロジェクトをオープンしたままで、 ファイル(F)→新規作成(N)→プロジェクト(P)でダイアログを表示して以下のように設定してください。

プロジェクトの種類(P) : Visual C++
テンプレート(T)      : MFCアプリケーション
プロジェクト名(N)      : IpcCli
ソリューション(S)     : ソリューションに追加
→OK

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

→次へ

アプリケーションの種類         : ダイアログベース(D)
ユニコードライブラリを使用する : チェックする
(他はデフォルト)
→次へ

ユーザインタフェイス機能     (デフォルトのまま)
→次へ

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

以上でスケルトンができました。

<リソースの作成>

最初に上図のようなダイアログを作成します。 メニューの表示(V)→その他のウィンドウ(E)→リソースビュー(R)からダイアログリソース (IDD_IPCCLI_DIALOG)をダブルクリックしてリソースを表示させてください。

各コントロールのプロパティは次のとおりです。

番号 コントロールのタイプ プロパティ
コンボボックス ID IDC_CMB_SRVPROCESS
Type ドロップダウンリスト
ボタン ID IDC_BTN_SRVLIST
Caption サーバリスト更新
スタティックテキスト ID IDC_STC_DSPCOL
Client Edge TRUE
スタティックテキスト ID IDC_STC_DSPNUM
Client Edge TRUE
ボタン ID IDC_BTN_GETCOL
Caption 色・数値取得
ボタン ID IDOK(デフォルト)
ボタン ID IDCANCEL(デフォルト)

<インポート管理クラスの実装>

サーバに対して接続をして、背景色・乱数値をリクエストして、共有メモリからデータを 取得するインポート管理クラスを作成します。サーバプロジェクトで エクスポート管理クラスを作成したときと同じように、 クラス名をCImportで作成してください。

ヘッダファイル・実装ファイルを以下のように記述してください。

//Import.h : CImport クラスヘッダ

//プロセス外であるIpcSrvデータのインポート(取得)クラス
#pragma once

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

class CImport
{
//メンバ変数///////////////////////////////////////////////////////////////////////
protected:
    HANDLE          m_hFileMap;           //ファイルマッピングオブジェクトハンドル
    SHARE_MEM       *m_pShareMem;         //共有メモリへのポインタ
    CEvent          *m_pEventRequest;     //データ要求イベント
    CEvent          *m_pEventComplete;    //共有メモリ書き込み
    CSemaphore      *m_pSemaRequest;      //データ要求セマフォ(要求権)

//メンバ関数///////////////////////////////////////////////////////////////////////
protected:
    void Terminate();                     //終了処理

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

    int   Initialize();                   //共有メモリのマッピング
    BOOL  Connect(DWORD_PTR dwProcessId); //サーバの対象プロセスに接続(イベント作成)
    BOOL  Execute(void  **ppData,    
                  DWORD & dwSize,    
                  const DWORD dwTimeout); //データ取得
};

実装ファイルを以下のように記述してください。

// Import.cpp : CImport クラスの実装

#include "StdAfx.h"
#include "Import.h"

//コンストラクタ
CImport::CImport() : m_hFileMap(NULL)
                   , m_pShareMem(NULL)
                   , m_pEventRequest(NULL)
                   , m_pEventComplete(NULL)
                   , m_pSemaRequest(NULL)
{
}

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

//終了処理
void CImport::Terminate()
{
    //各イベントClose
    if (m_pEventRequest)    {delete m_pEventRequest;    m_pEventRequest = NULL;}
    if (m_pEventComplete)   {delete m_pEventComplete;   m_pEventComplete = NULL;}
    if (m_pSemaRequest)     {delete m_pSemaRequest;     m_pSemaRequest = NULL;}

    //共有メモリアンマップ
    if (m_pShareMem)        {::UnmapViewOfFile(m_pShareMem);    m_pShareMem = NULL;}
    //ファイルマッピングオブジェクトClose
    if (m_hFileMap)         {::CloseHandle(m_hFileMap);         m_hFileMap = NULL;}
}

/*共有メモリのマッピング
戻値 -1 失敗 0 初期化済み 1 成功*/
int CImport::Initialize()
{
    int nRet = -1;

    //Initialize済みの時終了
    if (m_hFileMap && m_pShareMem)
        return 0;

    do
    {
        //共有メモリ作成
        m_hFileMap = ::CreateFileMapping(INVALID_HANDLE_VALUE,
                                         NULL,
                                         PAGE_READWRITE,
                                         0,
                                         sizeof(DWORD) + SHARED_DATA_SIZE,
                                         _T(IPC_FILEMAP_NAME));
        if (! m_hFileMap)
            break;

        //共有メモリの仮想アドレス空間へのマッピング
        m_pShareMem = (SHARE_MEM *)::MapViewOfFile(m_hFileMap, FILE_MAP_READ, 0, 0, 0);
        if (! m_pShareMem)
            break;

        //データ要求セマフォ構築
        m_pSemaRequest = new CSemaphore(1, 1, _T(SEMA_REQUEST_NAME), NULL);

        nRet = 1;
    } while(0);

    if (nRet < 0) //エラー時
        Terminate();

    return nRet;
}

/*サーバの対象プロセスに接続(イベント作成)
引数 dwProcessId サーバ側ウィンドウのプロセスID*/
BOOL CImport::Connect(DWORD_PTR dwProcessId)
{
    if (! m_hFileMap || ! m_pShareMem || ! m_pSemaRequest)
        return FALSE;

    //以前のイベントを削除
    if (m_pEventRequest)    {delete m_pEventRequest;     m_pEventRequest = NULL;}
    if (m_pEventComplete)   {delete m_pEventComplete;    m_pEventComplete = NULL;}

    //プレフィックスにサーバ側のプロセスIDを付加してイベント名作成>
    CString     strProcessId;
    strProcessId.Format(_T("%d"), dwProcessId);

    CString     strRequest(_T(EVENT_REQUEST_NAME));      strRequest += strProcessId;
    CString     strComplete(_T(EVENT_COMPLETE_NAME));    strComplete += strProcessId;

    //イベント作成
    m_pEventRequest = new CEvent(FALSE, FALSE, strRequest, NULL);
    m_pEventComplete = new CEvent(FALSE, FALSE, strComplete, NULL);

    return TRUE;
}

/*データ取得
引数  ppData     結果データを受取るポインタのアドレス
      dwSize     *ppDataのサイズ
      dwTimeout  サーバデータの取得までの待機時間(ミリ秒)
仕様  成功時は*ppDataはヒープに確保されているので呼出し側でdelete *ppDataを実行する事
      失敗時は*ppData, dwSizeは何も変更されてない*/
BOOL CImport::Execute(void **ppData, DWORD & dwSize, const DWORD dwTimeout)
{
    //初期化 & 接続確認
    if (! m_hFileMap || ! m_pShareMem || ! m_pEventRequest ||
        ! m_pEventComplete || ! m_pSemaRequest)
        return FALSE;

    BOOL         bRet = FALSE;
    CSingleLock  completelock(m_pEventComplete, FALSE); //共有メモリへの書込み完了イベント
    CSingleLock  semalock(m_pSemaRequest, FALSE);       //共有メモリアクセス権

    do
    {
        //他のプロセスからのデータ要求遮断のためセマフォ獲得
        if (! semalock.Lock(0))                 break;

        //サーバへのデータ要求(イベントシグナル- トリガ実行)
        if (! m_pEventRequest->SetEvent())      break;

        //共有メモリ書込み完了まで待機(タイムアウト時FALSE)
        if (! completelock.Lock(dwTimeout))     break;

        //データ取得サイズ確認
        if (! m_pShareMem->dwSize)              break;

        //データ用領域確保
        *ppData = new BYTE[m_pShareMem->dwSize];
        ::memset(*ppData, 0, dwSize);

        //データを*ppDataにコピー
        dwSize = m_pShareMem->dwSize;
        ::RtlCopyMemory(*ppData, m_pShareMem->data, dwSize);

        bRet = TRUE;
    } while(0);

    //データ要求イベント非シグナル化
    m_pEventRequest->ResetEvent();

    return bRet;
}

このクラスがクライアント側の中心クラスで 前項の図で機能の概略を書いていますが少し補足します。

<ダイアログクラスの実装>

最後にクライアント側のダイアログの実装を行います。 リソースを作成したときに大体その機能は創造できると思いますが、複数のサーバプロセスの 存在のための検索機能、CImportクラスによってサーバから取得したデータの表示機能です。 スケルトンで既にCIpcCliDlgクラスが出来上がっているはずなので以下のように記述してください。

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

#include "Import.h" //プロセス外データインポート管理クラス用ヘッダ

// CIpcCliDlg ダイアログ
class CIpcCliDlg : public CDialog
{
// ・
// ・
//メンバ変数///////////////////////////////////////////////////////////////////////////
protected:
    CBrush     m_brush;  //ID_STC_DSPCOLへの表示用ブラシ(取得色表示用ブラシ)
    CImport    m_import; //共有メモリ管理クラス(データ取得用)

//メンバ関数////////////////////////////////////////////////////////////////////////////
protected:
    //EnumWindowsコールバック関数 (サーバウィンドウ検索用)
    static BOOL CALLBACK EnumWinProc(HWND hWnd, LPARAM lParam);

public:
    ~CIpcCliDlg();                                  //デストラクタ

    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); //子コントロール描画
    afx_msg void   OnBnClickedBtnSrvlist();         //サーバリスト更新ボタンクリック
    afx_msg void   OnCbnSelendokCmbSrvprocess();    //サーバリストコンボ選択時
    afx_msg void   OnBnClickedBtnGetcol();          //色取得ボタンクリック

    DECLARE_MESSAGE_MAP()
};

続いて実装ファイルを以下のように記述してください。

// IpcCliDlg.cpp : 実装ファイル
//
#include "stdafx.h"
#include "IpcCli.h"
#include "IpcCliDlg.h"

// ・
// ・
// CIpcCliDlg ダイアログ

//コンストラクタ
CIpcCliDlg::CIpcCliDlg(CWnd* pParent /*=NULL*/)
: CDialog(CIpcCliDlg::IDD, pParent)
, m_brush( RGB(127, 127, 127) )
{
// ・
// ・
}

//デストラクタ
CIpcCliDlg::~CIpcCliDlg()
{
    m_brush.DeleteObject(); //m_brush GDIリソース解放
}

void CIpcCliDlg::DoDataExchange(CDataExchange* pDX)
{
// ・
// ・
}

BEGIN_MESSAGE_MAP(CIpcCliDlg, CDialog)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //子コントロール描画
    ON_WM_CTLCOLOR()
    //サーバリスト更新ボタンクリック
    ON_BN_CLICKED(IDC_BTN_SRVLIST, &CIpcCliDlg::OnBnClickedBtnSrvlist)
    //サーバプロセスコンボ選択時
    ON_CBN_SELENDOK(IDC_CMB_SRVPROCESS, &CIpcCliDlg::OnCbnSelendokCmbSrvprocess)
    //色取得ボタンクリック
    ON_BN_CLICKED(IDC_BTN_GETCOL, &CIpcCliDlg::OnBnClickedBtnGetcol)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CIpcCliDlg メッセージハンドラ
BOOL CIpcCliDlg::OnInitDialog()
{
// ・
// ・

    // プロセス外データインポート管理クラス初期化
    if ( m_import.Initialize() < 0 )
        ::AfxMessageBox(_T("外部データ取得オブジェクト初期化失敗- 通信不能"));

    //サーバプロセス検索
    OnBnClickedBtnSrvlist();

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

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

HCURSOR CIpcCliDlg::OnQueryDragIcon()
{
// ・
// ・
}

//スタティックコントロール描画ハンドラ(取得カラー表示用)
HBRUSH CIpcCliDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    if (pWnd->GetDlgCtrlID() == IDC_STC_DSPCOL && nCtlColor == CTLCOLOR_STATIC)
        hbr = m_brush; //IDC_STC_DSPCOLの背景を塗りつぶすブラシをm_brushにする

    return hbr;
}

//サーバリスト更新ボタンクリック
void CIpcCliDlg::OnBnClickedBtnSrvlist()
{
    CDWordArray     arrayProcessId;
    CComboBox       *pCombo = (CComboBox *)GetDlgItem(IDC_CMB_SRVPROCESS);
    BOOL            bErr = FALSE;

    //サーバ側ウィンドウのプロセスIDをarrayProcessIdに受取る
    if (! ::EnumWindows(CIpcCliDlg::EnumWinProc, (LPARAM)&arrayProcessId))
        return;

    //コンボ内容クリア
    pCombo->ResetContent();

    //取得したサーバ側ウィンドウのプロセスIDをコンボ項目に加える
    for (int i=0; i<arrayProcessId.GetCount(); i++)
    {
        CString     strProcessId;

        strProcessId.Format(_T("%d"), arrayProcessId.GetAt(i));

        //項目の文字列追加
        if ( pCombo->InsertString(i, strProcessId) < 0 )
            bErr = TRUE;

        //項目にプロセスIDを割当てる
        if (pCombo->SetItemData(i, arrayProcessId.GetAt(i)) == CB_ERR)
            bErr = TRUE;
    }
    if (bErr)
        ::AfxMessageBox(_T("サーバ側ウィンドウのプロセスID追加エラー"));
}

//EnumWindowsコールバック関数(サーバウィンドウ検索用)
BOOL CALLBACK CIpcCliDlg::EnumWinProc(HWND hWnd, LPARAM lParam)
{
    CDWordArray  *pArrayProcessId = reinterpret_cast<CDWordArray *>(lParam);
    CWnd         *pWnd = CWnd::FromHandle(hWnd); //列挙されたWindowの一時CWndポインタ
    CString      strCaption;                     //列挙されたWindow表題
    CString      strSrvCaption(_T(SRV_CAPTION)); //サーバ側Windowの表題プレフィックス

    //列挙されたWindow表題の取得
    pWnd->GetWindowText(strCaption);

    //取得されたWindow表題の最初の文字列がサーバ側Windowの表題プレフィックスと一致
    //(列挙されたhWndがIpcSrvのウィンドウである場合)
    if ( strCaption.Left( strSrvCaption.GetLength() ) == strSrvCaption )
    {
        DWORD dwProcessId = 0;

        //hWndのプロセスID取得
        ::GetWindowThreadProcessId(hWnd, &dwProcessId);
        //プロセスIDを呼出し元から渡された配列に追加
        pArrayProcessId->Add(dwProcessId);
    }

    return TRUE;
}

//サーバ側プロセスコンボ選択時
void CIpcCliDlg::OnCbnSelendokCmbSrvprocess()
{
    CComboBox     *pCombo = (CComboBox *)GetDlgItem(IDC_CMB_SRVPROCESS);
    DWORD_PTR     dwProcessId = 0;
    int           nIdx = pCombo->GetCurSel(); //選択された項目のIndex取得

    if (nIdx == CB_ERR)
        return;

    //CIpcCliDlg::OnBnClickedBtnSrvlistで割当てられたプロセスID取得
    dwProcessId = pCombo->GetItemData(nIdx);
    if (dwProcessId == CB_ERR)
        return;

    //対象のサーバに接続
    if (! m_import.Connect(dwProcessId) )
        ::AfxMessageBox(_T("サーバに接続失敗- 通信不能"));
}

//色・数値取得ボタンクリック
void  CIpcCliDlg::OnBnClickedBtnGetcol()
{
    BYTE     *pBuf = NULL;
    DWORD    dwSize = 0;
    BOOL     bRet = m_import.Execute( (void **)&pBuf, dwSize, 1500 ); //サーバデータ取得

    //スタティックコントロール表示用ブラシ変更
    m_brush.DeleteObject();
    m_brush.CreateSolidBrush( bRet ? *( (COLORREF *)pBuf ) : RGB(127, 127, 127) );

    //ダイアログ再描画
    Invalidate();

    //値表示
    CString strValue;

    if (bRet)
        strValue.Format(_T("%d"), *( (int *)(pBuf + sizeof(COLORREF)) ) );
    else
        strValue = _T("");

    GetDlgItem(IDC_STC_DSPNUM)->SetWindowTextW(strValue);

    //後始末
    if (pBuf)
        delete [] pBuf;

    if (! bRet)
        ::AfxMessageBox(_T("データ取得失敗"));
}

以上で出来上がったのでビルドして実行ですが、その前に2つのプロジェクトとも スタートアップするように、メニューの表示(V)→ソリューションエクスプローラ(P)から ソリューションエクスプローラ表示して、最上部の「ソリューション'IpcSrv'」を右クリックして プロパティ(R)を選択してプロパティページを表示させてください。
左ペインの「スタートアッププロジェクト」を選択して、マルチスタートアッププロジェクト(M) を選択し、IpcCli、IpcSrvの2つのプロジェクトのアクションを「開始」にしてください。
最後にIpcSrvが先に起動するように順番を変更してください。

参考文献 :

プロセス間共有メモリ
http://yokohama.cool.ne.jp/chokuto/advanced/sharedmem.html

CEventクラス
http://msdn.microsoft.com/ja-jp/library/efk30beh(v=VS.80).aspx