共有メモリを使用したプロセス間通信(ipcomm)


■概略説明
 これまでウィンドウズでプロセス同士での通信を行うプログラムは、殆どソケット通信でまかなっていました。ソケット通信は、確実ですがサーバー・クライアントの指定、ポートアドレスの指定などが必要なので、今回はもっとシンプルな通信を考えてみました。
 このサンプルは、タイトルの通り共有メモリを使い、データのやり取りをプロセス間で行うものです。データだけでしたら、これで終わりですが、ソケット通信と同様に受信通知をイベントにて行うために、内部は少し工夫がしてあります。
 使用方法はとても簡単で、ipcomm.exeを順番に2つ起動して下さい。最初に起動したプロセスがいわゆるサーバーとなりますが、プログラムレベルではサーバー・クライアントの違いはありません。
後は、お互いに送信メッセージを入力して、送信ボタンを押せば、相手のプロセスにメッセージが表示されます。

■プログラムの説明
 このサンプル内のクラス構造ですが、プロセス間通信はカプセル化された、CShcommクラスで管理されます。
このサンプルの主な通信仕様は、以下の通りです。
@ ASCII通信のみサポート(簡単な変更でバイナリも対応できるでしょう)
A 文字列長の制限は、NULLコードを含めて1024文字です。(DEFINEの値を書き換えれば変更できます)
B 3つ以上同時にプロセスが起動できるが、受信先がどこになるかはWindowsの管理によります。
(すなわち、マルチ通信には対応していません。1対1でのみ試してください)

 次に、このサンプル内のメイン側(CIpcommDlg)の処理を簡単に説明します。
CShcommはCIpcommDlgのメンバ変数(m_shcomm)として登録されています。
 通信の確立(初期化)は、CShcomm::Create()関数の呼び出しにより行われます。
        BOOL CIpcommDlg::OnInitDialog()
        {
                CDialog::OnInitDialog();
                        :
                        :
        
                // TODO: 特別な初期化を行う時はこの場所に追加してください。
                int                     pcount;
                if ( !m_shcomm.Create( "TestComm", this, pcount))
                {
                        AfxMessageBox( "通信開始エラー");
                }
                else
                {
                        CString         title;
                        title.Format( "ipcomm %d", pcount);
                        SetWindowText( title);
                }
        
                return TRUE;  // TRUE を返すとコントロールに設定したフォーカスは失われません。
        }
Create()関数の最初のパラメータは、共有メモリ通信のIDを意味し、通信を行う2つのプロセスは同じ名前をつけます。2番目のパラメータは、CShcommがメッセージ受信時に作成するユーザー定義のWindowメッセージを送るために使用するウィンドウのポインターでCIpcommDlg自身を示します。3番目は、現在確立された通信が何番目のプロセスかを示します。先に作成された場合は1になり、後から作成された場合は2が返されます。
 次に送信処理ですが、[送信]ボタンの処理内で行われています。
        void CIpcommDlg::OnOK() 
        {
                UpdateData();
        
                //////////////////////////////////////////////////////////////////
                //      データ送信.
                if ( m_shcomm.SendMessage( m_sSend))    // 送信成功.
                {
                        //////////////////////////////////////////////////////////////
                        //      送信データの表示.
                        CString                 cs;
        
                        cs.Format( "OUT '%s'", m_sSend);
                        m_cList.AddString( cs);
                        m_cList.SetCurSel( m_cList.GetCount() - 1);
                        UpdateData( FALSE);
                }
        }
送信処理は簡単で、SendMessage()関数に文字列を渡すだけです。この関数の戻り値がエラー(false)になる場合は、送信文字列長が0或いは最大長を超えた場合、相手のプロセスが無い場合、相手が送信処理中の場合です。
 次に受信処理ですが、前述したようにCShcommがウィンドウメッセージを使用して知らせてきます。ユーザーはこの処理をアプリケーションに追加します。メッセージIDはShcomm.hの上部にあります。
        //      親ウィンドウへの受信通知メッセージ
        //      他のユーザー定義メッセージと重複していないか、チェックしてください。
        #define WM_SHCOMM_RECEIVE                               ( WM_USER + 110)
サンプルではOnShcommReceive()という名前で処理ルーチンを追加します。
・ヘッダー内の追加
        //{{AFX_MSG(CIpcommDlg)
                :
        afx_msg LONG OnShcommReceive( UINT wParam, LONG lParam);
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
・CPP内のメッセージマップの追加
        BEGIN_MESSAGE_MAP(CIpcommDlg, CDialog)
                //{{AFX_MSG_MAP(CIpcommDlg)
                        :
                ON_MESSAGE( WM_SHCOMM_RECEIVE, OnShcommReceive)
                //}}AFX_MSG_MAP
        END_MESSAGE_MAP()
・CPP内の処理ルーチンの追加
        LONG CIpcommDlg::OnShcommReceive( UINT wParam, LONG lParam)
        {
                CString                 cs, msg;
        
                //////////////////////////////////////////////////////////////////
                //      受信データの取得.
                m_shcomm.ReceiveMessage( msg);
                //////////////////////////////////////////////////////////////////
                //      受信データの表示.
                cs.Format( "IN  '%s'", msg);
                m_cList.AddString( cs);
                m_cList.SetCurSel( m_cList.GetCount() - 1);
                UpdateData( FALSE);
                //////////////////////////////////////////////////////////////////
                //      受信データが終了命令ならばプログラムを終了する.
                if ( !msg.Compare( "exit"))
                        ::PostQuitMessage( 0);
                return 0;
        }
通信の終了処理は、CShcommのデストラクタで自動的に行われます。
■共有メモリ通信管理クラス
 通信管理クラス(CShcomm)の内容を、簡単に示します。
通信のデータ受渡しに関しては、共有メモリを使っていますが、データ送信には同期が必要です。共有メモリ上にフラグを立て、ポーリングにより行う方法もありますが、今回はウィンドウズのイベントオブジェクトを使用しました。イベントは待ち状態にあるプロセスにイベントを送ることにより同期を取ることができるプロセス間通信の手段の一つです。
また、もう一つの注意点として、データ領域のアクセスの排他があります。これは、2つのプロセスが同時にデータを書き込めないようにするという意味です。これも共有メモリ上のフラグを使えばできないことはないと思いますが、今回はセマフォを使用してみました。セマフォとは日本語で手旗信号という意味ですが、内容的には、むしろ通行許可書のようなものです。すなわち、通行許可書が指定した数(サンプルでは1個)用意されており、通行者(プロセス)は部屋(共有メモリ)に入る(アクセスする)ために、入り口で通行許可書を取り、中に入り仕事をして出口でそれを返します。つまり、中に先に入った人が居た場合、後からきた人は、その人の仕事が終わるまで中に入れなくなります。
 また、イベントオブジェクトの受け取りは、通常のウィンドウメッセージと違いWaitForSingleObject()関数で検査・待機しなければなりません。そのため監視は、その専用スレッドを作成して行っています。
その他、送信用・受信処理完了用のイベントオブジェクトがありますが、図の方が分かりやすいので下の図を見てください。

共有メモリ使用プロセス間通信の処理概略・タイミング図
送信用以外のオブジェクトは、全てプロセス間で共通です。送信イベントだけは、プロセス毎に違う名前を付けてあり、そのプロセスでしか使用できなくしてあります。
 バイナリ通信や、マルチプロセス間通信にも応用できると思います。必要に応じて改造してください。
■ファイルのダウンロード
(MFC6.0プロジェクトファィル17KB)