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

[ ホーム ] [ 上へ ] [ プロセス間通信 VC++ 2005 MFC その3 ]

・アプリケーション作成

今回掲載するアプリケーションは1つのソリューションに 2つのプロジェクトを作成します。プロセス間通信ですから、当然2つのEXEファイルが必要です。 もちろん、1つのEXEでデータの送信/受信の両機能を盛り込むことも可能ですが、流れがわかりにくい のであえてサーバー機能を持ったEXEとクライアント機能を持ったEXEの2つを作成します。
どういうアプリかというと、サーバ側EXEがSDIウィンドウで一定時間間隔でクライアント領域の 背景色が変化させ、同時に乱数を表示します。クライアント側EXEがダイアログベースでサーバ側の 現在の背景色と乱数値を取得して表示するだけのごく簡単なものです。
プログラムの中身はクライアント側から現在のサーバ側の背景色・乱数値のリクエストをすると、 サーバ側は共有メモリに書き込んで、それを取得してクライアント側のダイアログの スタティックコントロールに表示するというものです。この中で同期オブジェクトは、イベント、 セマフォを用います。 クリティカルセクションに関しては説明はしますが、 実際にプログラムには使いません。ミューテックスは前頁の「二重起動の防止」の説明だけとします。
この後コードを掲載しますが、それだけではデータの流れ等よくわかないかもしれないので、 概略を先に図示します。

実線がデータの流れ(サーバのクライアント領域の背景色データ)、破線が命令系統の流れです。

  1. ・・・クライアント側からメインウィンドウのタイトル・プロセスIDからサーバ側プロセスに接続。(実際にはサーバ側と同名のイベント同期オブジェクトを作成します。)
  2. ・・・現在のサーバ側の背景色・乱数値を取得したいときにCImport::Executeを呼んでデータを要求します。この要求は、サーバ側に共有メモリに現在の表示色・乱数値を書き込むようにする要求で、サーバ側のセカンダリスレッドでCEventオブジェクトが待機しているので以降のコードを実行するようSetEventを実行しています。その後、サーバ側から共有メモリへのデータ書込み完了の通知(F)がくるまで待機しています。
  3. ・・・クライアントからデータ要求がきて、(B)のSetEventよりサーバ側のセカンダリスレッドが解放されたら、ビューウィンドウに対してクライアントより現在の背景色・乱数値を共有メモリに書き込むように要求がきたことをウィンドウメッセージで送ります。送信するメッセージはCExport::Initialize時にビューウィンドウへのポインタを取得していて、結果的にCExportの管理の元でビュークラスにメッセージを送信しています。
  4. ・・・メッセージを受取ったビュークラスはエクスポート管理クラスに現在の背景色・乱数値を渡し、エクスポート管理クラスは共有メモリに書き込みます。
  5. ・・・共有メモリに書き込んだ段階で、(C)のウィンドウメッセージの送信から制御が戻り、(B)でデータ書込み完了を待っていたCImportクラスに通知します。この通知も(B)でイベントが待機していてSetEventでその待機状態を解放することによって実現しています。図ではセカンダリスレッドから矢印がでていますが、実際はこのスレッドが構築される際にCExportオブジェクトのポインタを渡していて、CExportクラスによってクライアントに通知しています。サーバ側セカンダリスレッドはクライアントにデータ書込み完了を通知したら再びデータ要求のために待機状態になります。
  6. ・・・(F)でCImportのスレッドが解放されたので、書き込まれた共有メモリからデータを取得してダイアログのコントロールに色・乱数値を表示します。

では、早速作成していきます。

<サーバプログラム作成>

まず、サーバ側のプロジェクトから作成します。
VSのメニューからファイル(F)→新規作成(N)→プロジェクト(P)でダイアログが表示されるので 次のようなプロジェクトを作成してください。

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

→OK

アプリケーションウィザードで以下のようにしてください。

アプリケーションの種類                      : シングルドキュメント(S)
ドキュメントビューアーキテクチャーサポート  : チェックをはずす
ユニコードライブラリを使用する            :  チェックする
プロジェクト形式                       : MFC標準
MFCの使用法                            : 共有DLLでMFC使用
→次へ

データベースサポート      : なし
→次へ

メインフレームのスタイル : サイズ変更(T)、最小化ボタン(I)、
                              最大化ボタン(A)、システムメニュー(S)にチェック。
ツールバー             : なし(O)
→次へ

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

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

<共有情報用のヘッダ作成>

サーバプログラムができた後、クライアントプログラムを作成しますが、通信するので データ共有のための情報が必要になるので先にそのヘッダファイルを作成します。このファイルを サーバ側・クライアント側でインクルードすることになります。

メニューの表示(V)→ソリューションエクスプローラ(P)でソリューションエクスプローラを 表示してください。
ヘッダーファイルフォルダを右クリックして追加(D)→新しい項目(W)でダイアログを表示して 以下のヘッダファイルを作成してください。

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

以下の記述をしてください

//プロセス間通信用ヘッダ
//(同期オブジェクト名, 共有メモリ、ファイルマッピングオブジェクト名)
#ifndef _IPCSHARE_H_
#define _IPCSHARE_H_

//サーバ側Window表題プレフィックス(サーバ側Window表題はプレフィックスにプロセスIDを付加)
#define    SRV_CAPTION              "IpcSrv-"

//ファイルマッピングオブジェクト名
#define    IPC_FILEMAP_NAME         "IpcFileMap"

//同期用イベント名
//Clientよりデータ要求イベント名(Server, Client両者で利用)
#define    EVENT_REQUEST_NAME       "EventIpcSrvRequest_"
//Severから共有メモリ書換えイベント名(Server, Client両者で利用)
#define    EVENT_COMPLETE_NAME      "EventIpcSrvComplete_"
//Serverのセカンダリスレッド終了イベント名(Serverのみ)
#define    EVENT_TERMINATE_NAME     "EventIpcSrvTerminate_"

//データ要求セマフォ名(要求権) (Clientのみ)
#define    SEMA_REQUEST_NAME        "SemaphoreIpcCliRequest"

//共有メモリ定義
typedef struct
{
    DWORD   dwSize;      //データサイズ
    BYTE    data[1];     //可変長データ
} SHARE_MEM;

//共有最大データサイズ
#define    SHARED_DATA_SIZE    ( sizeof(COLORREF) + sizeof(int) ) //必要に応じてサイズ変更

#endif //_IPCSHARE_H_

<メインフレームクラスの実装>

まず、フレームウィンドウクラスの実装から。MainFrm.cppに以下のコードを記述してください。

// MainFrm.cpp : CMainFrame クラスの実装
#include "IpcShare.h"      //プロセス間通信用ヘッダ
//・
//・
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

//ウィンドウ表題設定(クライアントからこのウィンドウの表題検査するためプレフィックス設定)
    CString     strCaption( _T(SRV_CAPTION) );
    CString     strProcessId;

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

    SetWindowText(strCaption);

// フレームのクライアント領域全体を占めるビューを作成します。
    if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
        CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL))
    {
        TRACE0("ビューウィンドウを作成できませんでした。\n");
        return -1;
    }
    return 0;
}

CMainFrame::OnCreateにウィンドウのタイトルを設定します。 クライアントからサーバを検索するとき に、このタイトル名(SRV_CAPTION + プロセスID)を使って行います。

<データエクスポート管理クラスの実装>

今回のサーバ側のメインになるクラスです。クライアントからの背景色要求リクエストに 対して、現在の背景色を共有メモリに書き込んで再びクライアントに通知します。

メニューのプロジェクト(P)→クラスの追加(C)で ダイアログボックスを表示して以下のようにしてください。

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

クラス名         : CExport       (他はデフォルト)
→完了

CExportクラスができているので ヘッダファイル・実装ファイルを以下のように記述してください。

//Export.h : CExport クラスヘッダ

//IpcSrvデータのプロセス外エクスポートクラス
//(ワーカスレッドを作成し、共有メモリで他プロセス要求データをエクスポート)
#pragma once

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

//親ウィンドウへのデータ取得要求メッセージ
//(データ要求されたメッセージハンドラ内でデータ取得成功時は0, 失敗時は1を返却)
#define     WM_REQUEST_DATA     (WM_USER + 11)

class CExport
{
//メンバ変数////////////////////////////////////////////////////////////////////////////
protected:
    HANDLE         m_hFileMap;         //ファイルマッピングオブジェクトハンドル
    SHARE_MEM      *m_pShareMem;       //共有メモリへのポインタ

    CEvent         *m_pEventRequest;   //IpcSrvデータ要求イベント(トリガ)
    CEvent         *m_pEventComplete;  //共有メモリ書込み完了イベント
    CEvent         *m_pEventTerminate; //セカンダリスレッド終了イベント
    CWinThread     *m_pThread;         //セカンダリスレッドへのポインタ

    CWnd           *m_pParent;         //このクラスの親ウィンドウ(ビューウィンドウ)

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

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

    int Initialize(CWnd *pParent);                 //初期化
    BOOL CopyToSharedMemory(const void *pData,     
                            const DWORD dwSize);   //データを共有メモリにコピー

    static UINT __cdecl ExportProc(LPVOID pParam); //セカンダリスレッド制御関数
};

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

// Export.cpp : CExport クラスの実装

#include "StdAfx.h"
#include "Export.h"

//コンストラクタ
CExport::CExport() : m_hFileMap(NULL)
                   , m_pShareMem(NULL)
                   , m_pEventRequest(NULL)
                   , m_pEventComplete(NULL)
                   , m_pEventTerminate(NULL)
                   , m_pThread(NULL)
                   , m_pParent(NULL)
{
}

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

//終了処理
void CExport::Terminate()
{
    //セカンダリスレッド終了
    if (m_pEventTerminate && m_pThread)
    {
        m_pEventTerminate->SetEvent();
        ::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
        delete m_pThread;
        m_pThread = NULL;
    }

    //ファイルのビューのマッピング解除
    if (m_pShareMem)          {::UnmapViewOfFile(m_pShareMem);  m_pShareMem = NULL;}

    //ファイルマップオブジェクトハンドルClose
    if (m_hFileMap)           {::CloseHandle(m_hFileMap);       m_hFileMap = NULL;}

    //イベント削除
    if (m_pEventRequest)      {delete m_pEventRequest;          m_pEventRequest = NULL;}
    if (m_pEventComplete)     {delete m_pEventComplete;         m_pEventComplete = NULL;}
    if (m_pEventTerminate)    {delete m_pEventTerminate         m_pEventTerminate = NULL;}

    //親ウィンドウ登録解除
    if (m_pParent)            m_pParent = NULL;
}

/*初期化(データエクスポート用セカンダリスレッド開始
         共有オブジェクト構築・マッピング
         同期オブジェクト(イベント)作成)
引数  pParent 親ウィンドウへのポインタ(IpcSrvのビューウィンドウへのポインタ)
戻値  失敗-1 初期化済み0 成功1 */
int CExport::Initialize(CWnd *pParent)
{
    if (! pParent)
        return -1;

    //初期化済みの時終了
    if (m_pThread && m_hFileMap && m_pShareMem &&
        m_pEventRequest && m_pEventComplete && m_pEventTerminate
    )
        return 0;

    int     nRet = -1;

    do
    {
        if (m_pThread || m_hFileMap || m_pShareMem ||
            m_pEventRequest || m_pEventComplete || m_pEventTerminate
        )
            break;

        //ファイルマッピングオブジェクト作成
        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_WRITE, 0, 0, 0);
        if (! m_pShareMem)
            break;

        //各イベント作成(各イベントプレフィックスに
        //プロセスIDを付加したものをイベント名にする)
        CString     strProcessId;
        strProcessId.Format( _T("%d"), ::GetCurrentProcessId() );

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

        m_pEventRequest   = new CEvent(FALSE, FALSE, strRequest, NULL);
        m_pEventComplete  = new CEvent(FALSE, FALSE, strComplete, NULL);
        m_pEventTerminate = new CEvent(FALSE, FALSE, strTerminate, NULL);

        //セカンダリスレッド開始
        m_pThread = AfxBeginThread(ExportProc, this, 0, 0, CREATE_SUSPENDED, NULL);
        m_pThread->m_bAutoDelete = FALSE;
        m_pThread->ResumeThread();

        //親ウィンドウ登録
        m_pParent = pParent;

        nRet = 1;
    } while(0);

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

    return nRet;
}

/*親ウィンドウ内のデータを共有メモリにコピー
引数  pData   共有メモリに格納するデータ
      dwSize  pDataのバイト数
戻値  失敗時は共有メモリは0でクリアされる*/
BOOL CExport::CopyToSharedMemory(const void *pData, const DWORD dwSize)
{
    if (! m_pShareMem)
        return FALSE;

    BOOL     bRet = FALSE;
    do
    {
        if (! pData || ! dwSize)
            break;

        //共有メモリのサイズがコピー元のサイズより小さい時はFALSE
        if ( dwSize > SHARED_DATA_SIZE )
            break;

        m_pShareMem->dwSize = dwSize;                        //dataのサイズ
        ::RtlCopyMemory(m_pShareMem->data, pData, dwSize);   //dataのコピー

        bRet = TRUE;
    } while(0);

    if (! bRet) //失敗時はで共有メモリ初期化
        ::memset(m_pShareMem, 0, sizeof(DWORD) + SHARED_DATA_SIZE);

    return bRet;
}

/*セカンダリスレッド制御関数
引数pParam CExportへのポインタ(CExport::Initialize内のAfxBeginThreadを参照)*/
UINT CExport::ExportProc(LPVOID pParam)
{
    CExport       *pExport = static_cast<CExport *>(pParam);
    CSyncObject   *pSync[] = {pExport->m_pEventRequest, pExport->m_pEventTerminate};
    CMultiLock    multilock(pSync, sizeof pSync / sizeof pSync[0], FALSE);

    //m_pEventRequestイベントがシグナルである間ループ
    //(m_pEventRequest, m_pEventTerminateのどちらかがシグナルになるまで待機)
    while( multilock.Lock(INFINITE, FALSE, 0) == WAIT_OBJECT_0 )
    {
        //WM_REQUEST_DATAメッセージを親ウィンドウへ送り、共有メモリへデータ格納要求
        if (pExport->m_pParent->SendMessage(WM_REQUEST_DATA, NULL, NULL) )
            continue;

        //共有メモリにデータ書込み完了イベントシグナル化
        pExport->m_pEventComplete->SetEvent();
    }
    AfxEndThread(0, FALSE);

    return 0;
}

このクラスがサーバ側の中心的な部分で上図で機能の概略は 書いていますが、もう少し詳しく書いてみます。このクラスのインスタンスが作られ (以下でビューウィンドウのOnCreateでインスタンス化されます)、Initializeを実行すると、 ファイルマッピングオブジェクトの作成、マッピングを行い、同時に各イベントの作成をして、 最後にセカンダリスレッドを起動してクライアントからのデータ要求を待機するようにします。 本来はセカンダリスレッド作成などを含むマルチスレッドのことを先に書かないといけないのですが、 さらにややこしくしたくないので、とりあえす「こう作る方法もある」程度で流してください。 いずれ書くかもしれません。
ということで、Initializeを呼ぶと上図のような状態でクライアントからの 要求を待機できるようになります。

セカンダリスレッドはCExport::ExportProc関数で制御され、 スレッド終了イベント(m_pEventTerminate)がシグナルになるまで無限ループになっていて データ要求に対応するようになっています。

クライアントから要求がきたら、ループ内に制御が解放され、親ウィンドウにデータの 要求が来たことを通知するメッセージ(WM_REQUEST_DATA)を送りますが、SendMessageで送っているので 親ウィンドウ側(ビュークラス)でメッセージハンドラの処理が済むまで制御が戻りません。 親ウィンドウのメッセージハンドラ内で処理されるのがCExport::CopyToShareMemoryです。 ここで、共有メモリにデータを書き込んでいます。
メッセージハンドラの処理が終わり、制御が返ると書き込み完了のイベント(m_pEventComplete)を SetEventして、クライアント側で待機しているスレッドに通知します。

<ビュークラスの実装>

ビュークラスでは一定間隔で背景色を変更する機能と、セカンダリスレッドから届いた データ要求に対してデータエクスポート管理クラス(CExport)が、 共有メモリにデータを書き込む機能を加えます。

まず、ChildView.hヘッダファイルを次のように定義してください。

// ChildView.h : CChildView クラスのインターフェイス
//
#pragma once

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

// CChildView ウィンドウ
class CChildView : public CWnd
{
// ・
// ・
// ・
protected:
    CBrush                        m_brush;        //Viewの背景色
    int                           m_nValue;       //ウィンドウ表示値

    CArray<COLORREF, COLORREF>    m_colpattern;   //背景色パターン

    CExport                       m_export;       //共有メモリ管理クラス

public:
    //背景色変更用タイマ
    afx_msg void OnTimer(UINT_PTR nIDEvent);
    //ビュー構築時
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //ビュー破壊時
    afx_msg void OnDestroy();
    //セカンダリスレッドからのデータ要求メッセージハンドラ
    afx_msg LRESULT OnRequestData(WPARAM wParam, LPARAM lParam);

    DECLARE_MESSAGE_MAP()
};

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

// ChildView.cpp : CChildView クラスの実装
//
#include "stdafx.h"
#include "IpcSrv.h"
#include "ChildView.h"

// ・
// ・

// CChildViewコンストラクタ
CChildView::CChildView()
: m_brush( RGB(255, 255, 255) ) //背景色用ブラシ初期化
, m_nValue(0)                   //ウィンドウ表示値初期化
{
    //背景色パターン登録
    m_colpattern.Add( RGB(255, 0, 0 ) );
    m_colpattern.Add( RGB(255, 255, 0 ) );
    m_colpattern.Add( RGB(0, 255, 0 ) );
    m_colpattern.Add( RGB(0, 255, 255) );
    m_colpattern.Add( RGB(0, 0, 255) );
    m_colpattern.Add( RGB(255, 0, 255) );
    m_colpattern.Add( RGB(255, 255, 255) );

    ::srand( (unsigned)::time(NULL) ); //乱数初期化
}

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

BEGIN_MESSAGE_MAP(CChildView, CWnd)
    ON_WM_PAINT()
    ON_WM_TIMER()
    ON_WM_CREATE()
    ON_WM_DESTROY()
    //セカンダリスレッドからのデータ要求
    ON_MESSAGE(WM_REQUEST_DATA, &CChildView::OnRequestData)
END_MESSAGE_MAP()

// ウィンドウ構築前
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
// ・
// ・
}

//Window描画
void CChildView::OnPaint()
{
    CPaintDC     dc(this);                   // 描画のデバイスコンテキスト
    CRect        rect;
    CString      strValue;

    GetClientRect(&rect);
    dc.FillRect(&rect, &m_brush);            //指定のブラシで描画

    strValue.Format(_T("%d"), m_nValue);
    rect.DeflateRect(rect.Width() / 4, rect.Height() / 4);
    dc.SetBkMode(TRANSPARENT);
    dc.DrawText(strValue, &rect, DT_CENTER); //乱数値表示
}

//Window構築時
int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // 背景色切替え用タイマセット
    if (! SetTimer(1, 2000, NULL) )
        return -1;

    //共有データ管理クラス構築
    if (! m_export.Initialize(this) )
        ::AfxMessageBox(_T("共有データ管理オブジェクト構築失敗 - 通信不能"));

    return 0;
}

//Window破壊時
void CChildView::OnDestroy()
{
    CWnd::OnDestroy();
    KillTimer(1);      //タイマ終了
}

//タイマ
void CChildView::OnTimer(UINT_PTR nIDEvent)
{
    static INT_PTR nIdx = 0;

    //背景色パターン切替
    if ( ++nIdx > m_colpattern.GetUpperBound() )
        nIdx = 0;

    //ブラシ作成
    m_brush.DeleteObject();
    m_brush.CreateSolidBrush( m_colpattern.GetAt(nIdx) );

    //ウィンドウ表示値設定
    m_nValue = ::rand();

    //ビュー再描画
    Invalidate();
}

/*共有データ要求ハンドラ
戻値   共有データに正常に書き込めた時0、それ以外1*/
LRESULT CChildView::OnRequestData(WPARAM wParam, LPARAM lParam)
{
    LRESULT     nRet = 1;
    LOGBRUSH    lb;
    BYTE        *pBuf = new BYTE[SHARED_DATA_SIZE];

    do
    {
        ::memset( &lb, 0, sizeof(LOGBRUSH) );
        ::memset( pBuf, 0, SHARED_DATA_SIZE );

        //m_brushの色のRGB値が格納されているLOGBRUSH構造体取得
        if ( ! m_brush.GetLogBrush(&lb) )
            break;

        *( (COLORREF *)pBuf ) = lb.lbColor;                  //背景色取得
        *( (int *)(pBuf + sizeof(COLORREF)) ) = m_nValue;    //乱数値取得

        //背景色・乱数値を共有メモリにコピー
        if ( ! m_export.CopyToSharedMemory(pBuf, SHARED_DATA_SIZE) )
            break;

        nRet = 0;
    } while(0);

    delete [] pBuf;

    return nRet;
}

(クリティカルセクションについて)

今回はセカンダリスレッドからメッセージを飛ばして、プライマリスレッドで 背景色データ(m_brush)・乱数値(m_nValue)にアクセスしているので、同期処理は必要ないですが (メッセージ同期機構)、もし、セカンダリスレッド内で直接プライマリスレッドで作成された CChildViewのメンバであるm_brush、m_nValueにアクセスする時は同期処理が必要になります。
プリマリスレッドのCChildView::OnTimerで一定間隔でm_brush、m_nValueが書き換えられ、 セカンダリスレッドでそのm_brush、m_nValueにアクセスされることになりますが、両スレッドは 各々単独で動いているので、例えばOnTimerでブラシがDeleteObjectされた瞬間にセカンダリスレッドから m_brushをされると不味いからです。
この場合はクリティカルセクションを使います。その場合m_brush、m_nValueが書き換えられる CChildView::OnTimerで次のような感じでCCriticalSectionを使わなければならないでしょう。

static INT_PTR    nIdx = 0;
CSingleLock     slock(&m_critsec);  //既にCCriticalSectionクラスの
                                    //m_critsecが構築されている

slock.Lock();
if ( slock.IsLocked() )
{
    if ( ++nIdx > m_colpattern.GetUpperBound() )
        nIdx = 0;

    m_brush.DeleteObject();
    m_brush.CreateSolidBrush( m_colpattern.GetAt(nIdx) );

    m_nValue = ::rand();
    slock.Unlock(); //m_brushリソース解放

    Invalidate(); //再描画
}

もちろん、セカンダリスレッド側も同じようにCCriticalSectionを使って同期をしなければなりません。

以上でサーバ側の実装ができたので、とりあえずビルド後実行してクライアント領域の背景色・乱数値が 変化していくかを確認してください。続いて、クライアントアプリケーションの作成に移ります。