ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

MFCで他プロセスのウィンドウにメッセージを送る方法

はじめに

MFCアプリケーションで他プロセスのメインウィンドウにメッセージを送ってみましょう. 


送信用のプログラムではSendMessage()またはPostMessage()を使います.


受信用のプログラムではPreTranslateMessage()を使います. MFCでもWndProcをオーバーライドできるようですが、手続きが大変そうなので今回は扱いません.


最後に、共有メモリを使って詳細なデータをやり取りできるようにします.

環境

項目 バージョン
OS Windows11
Visual Studio 2017 Community Edition (ツールセットv141)
Windows SDK 10.0.17763.0

送信部

  1. SendMsgという名前でMFCアプリケーションプロジェクトを作成
  2. ダイアログのメンバにウィンドウハンドラhWndを定義
  3. ダイアログの初期化時に受信用プロセスのメインウィンドウハンドラを取得
  4. メインダイアログ上にメッセージ送信用のボタンを配置、ID_BUTTONSENDとする.
  5. メインダイアログ内にID_BUTTONSENDのBN_CLICKEDイベントに対するハンドラを定義
  6. ハンドラ内でhWndにSendMessage()する. メッセージはビルトインのものでも、適当な数値でも良い.

変更した箇所は次のようになります.

// SendMsgDlg.h
class CSendMsgDlg : public CDialogEx
{
    // 省略
protected:
    // 追加
    HANDLE hRecv;
    HWND hWnd;
    afx_msg void OnBnClickedButton1();
}

// SendMsgDlg.cpp

BEGIN_MESSAGE_MAP(CSendMsgDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BUTTONSEND, &CSendMsgDlg::OnBnClickedButton1)
END_MESSAGE_MAP()

BOOL CSendMsgDlg::OnInitDialog()
{
        // 省略
    // TODO: 初期化をここに追加します。

        // RecvMsgを起動して起きそのPIDを設定する
    DWORD dwProcId= 11111;

        // dwProcIdをPIDにもつプロセスのプロセスハンドルを取得
        // 取得できなかった場合はプログラム即終了
    hRecv = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
    if (hRecv == NULL) {
        AfxMessageBox(_T("Recv Proc Not Found\n"));
        exit(1);
    }

    // 現在開いているプロセスのメインウインドウを取得
        // そのウインドウハンドラががdwProcIdのPIDのプロセスにアタッチされたものであればそれが求めるべきウィンドウハンドラ
    hWnd = ::GetTopWindow(NULL);
    do {
        if (GetWindowLong(hWnd, GWL_HWNDPARENT) != 0 || !::IsWindowVisible(hWnd))
            continue;
        DWORD ProcessID;
        GetWindowThreadProcessId(hWnd, &ProcessID);
        if (dwProcId == ProcessID) {
            TRACE("--------------------------- PID %d  Found --------------------------- \n",(int)ProcessID);
            break;
        }
    } while ((hWnd = ::GetNextWindow(hWnd, GW_HWNDNEXT)) != NULL);

        // 共有メモリの処理(後ほど)

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

void CSendMsgDlg::OnBnClickedButton1()
{
    TRACE(" --------------------------- hWnd %p --------------------------- \n",hWnd);
    // TODO: ここにコントロール通知ハンドラー コードを追加します。
    ::SendMessage(hWnd, WM_CLOSE, 0, 0);
 // ::SendMessage(hWnd, WM_USER + 1, 0, 0);
}

::SendMessage()ですが、::(スコープ解決演算子)をつけないとCWnd::SendMessage()が呼び出されてしまいます. CWnd::SendMessage()は引数にウィンドウハンドルをとらず自身にメッセージを送信する関数なので、今回のようにウィンドウハンドルを指定してメッセージを送りたいときには::SendMessage()を使いましょう.

受信部

  1. RecvMsgという名前でMFCアプリケーションプロジェクトを作成
  2. ダイアログでPreTranslateMessage()をオーバーライドする
  3. 独自の応答をさせたいメッセージごとに処理を実装
  4. それ以外のメッセージに対しては基底クラスのPreTranslateMessage()を呼び出す.

変更した箇所は次のようになります.

// RecvMsgDlg.h
class CRecvDlg : public CDialogEx
{
    // 省略
protected:
    virtual BOOL PreTranslateMessage(MSG* pMsg) override;
};

// RecvMsgDlg.cpp
BOOL CRecvMsgDlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_CLOSE) {
        AfxMessageBox(_T("WM_CLOSE"));
    }
    else if (pMsg->message == WM_USER + 1) {
        AfxMessageBox(_T("USER DEFINE"));
    }else {
        return CDialogEx::PreTranslateMessage(pMsg);
    }

    return TRUE;
}

以上で、SendMsg側のボタンを押すとRecvMsg側のダイアログが表示されます. なお、親クラスのPreTranslateMessage()を呼び忘れるとダイアログが一切のメッセージを受け取らず、ボタンクリックどころかウインドウを移動させることもできなくなるので注意です.

共有メモリを利用できるようにする

別プロセスのウィンドウにメッセージを送るのは簡単にできますが、このままではデータのやり取りが出来ずかゆいところに手が届きません. 大量のデータを複数のウィンドウで共有するためには以下の方法がまず考えられます.

  1. 同一プロセスで複数のウィンドウを扱う
  2. 共有メモリを利用し異なるプロセスから同じデータにアクセスできるようにする.


今回は後者の共有メモリを利用しましょう. 共有メモリを使う手順は次のようになります.

  1. ファイルマッピングオブジェクトを共有メモリ用に作成 (CreateFileMapping() )
  2. ファイルマッピングオブジェクトへのハンドルを取得(CreateFileMapping() )
  3. プロセスのメモリにマッピング(MapViewOfFile() )

1.と2. を同時に行ってくれるAPI CreateFileMapping() はパラメタが多すぎるのでこちらを参考にさせていただきました.

変更した箇所は次のようになります.

// SendMsgDlg.h
class CSendMsgDlg : public CDialogEx
{
    // 省略
protected:
    // 共有メモリ用に追加
    HANDLE hSharedMem;
        int* pSharedMem;
};


// SendMsgDlg.cpp
BOOL CSendMsgDlg::OnInitDialog()
{
        // 省略
        // 共有メモリの処理
    
        // 共有メモリサイズ: 1024Byte
        DWORD dwSharedMemorySize = 1024;

        // ファイルマッピングオブジェクトを共有メモリ用に作成
        hSharedMem =::CreateFileMapping(
              INVALID_HANDLE_VALUE  // ファイルハンドル( 共有メモリの場合は、0xffffffff(INVALID_HANDLE_VALUE)を指定 )
            , NULL                  // SECURITY_ATTRIBUTES構造体
            , PAGE_READWRITE        // 保護属性( PAGE_READONLY / PAGE_READWRITE / PAGE_WRITECOPY, SEC_COMMIT / SEC_IMAGE / SEC_NOCACHE / SEC_RESERVE )
            , 0                     // ファイルマッピング最大サイズ(HIGH)
            , dwSharedMemorySize    // ファイルマッピング最大サイズ(LOW)
            , L"TR_SHARED_MEM" // 共有メモリ名称
        );

        // ファイルマッピングオブジェクトをプロセスのメモリ空間にマッピング
        // 返り値の型がLPVOIDなので、int型にキャストする
        pSharedMem = (int*)::MapViewOfFile(
        hSharedMem         // ファイルマッピングオブジェクトのハンドル
        , FILE_MAP_WRITE        // アクセスモード( FILE_MAP_WRITE/ FILE_MAP_READ / FILE_MAP_ALL_ACCESS / FILE_MAP_COPY )
        , 0                     // マッピング開始オフセット(LOW)
        , 0                     // マッピング開始オフセット(HIGH)
        , dwSharedMemorySize    // マップ対象のファイルのバイト数
    );

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

void CSendMsgDlg::OnBnClickedButton1()
{
    TRACE("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& %p\n",hWnd);
    // TODO: ここにコントロール通知ハンドラー コードを追加します。
        pSharedMem[0] = 1;
        pSharedMem[1] = 2;
        pSharedMem[2] = 3;
    //::SendMessage(hWnd, WM_CLOSE, 0, 0);
        ::SendMessage(hWnd, WM_USER + 1, 0, 0);

}

// RecvMsgDlg.h
class CRecvDlg : public CDialogEx
{
    // 省略
protected:
    // 共有メモリ用に追加
    HANDLE hSharedMem;
    int* pSharedMem;
};

// RecvMsgDlg.cpp
BOOL CRecvDlg::OnInitDialog()
{
    // 省略
    // TODO: 初期化をここに追加します。
    // 共有メモリサイズ: 1024Byte
    DWORD dwSharedMemorySize = 1024;

    hSharedMem = ::CreateFileMapping(
        INVALID_HANDLE_VALUE  // ファイルハンドル( 共有メモリの場合は、0xffffffff(INVALID_HANDLE_VALUE)を指定 )
        , NULL                  // SECURITY_ATTRIBUTES構造体
        , PAGE_READWRITE        // 保護属性( PAGE_READONLY / PAGE_READWRITE / PAGE_WRITECOPY, SEC_COMMIT / SEC_IMAGE / SEC_NOCACHE / SEC_RESERVE )
        , 0                     // ファイルマッピング最大サイズ(HIGH)
        , dwSharedMemorySize    // ファイルマッピング最大サイズ(LOW)
        , L"TR_SHARED_MEM" // 共有メモリ名称
    );

    pSharedMem = (int*)::MapViewOfFile(
        hSharedMem         // ファイルマッピングオブジェクトのハンドル
        , FILE_MAP_READ        // アクセスモード( FILE_MAP_WRITE/ FILE_MAP_READ / FILE_MAP_ALL_ACCESS / FILE_MAP_COPY )
        , 0                     // マッピング開始オフセット(LOW)
        , 0                     // マッピング開始オフセット(HIGH)
        , dwSharedMemorySize    // マップ対象のファイルのバイト数
    );

    if (hSharedMem == NULL) {
        AfxMessageBox(_T("Error on Creating Shared Memory"));
        exit(1);
    }

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

BOOL CRecvDlg::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_USER + 1) {
        for (int i = 0; i < pSharedMem[2]; i++) {
            AfxMessageBox(_T("aaa"));
        }
    }else {
        return CDialogEx::PreTranslateMessage(pMsg);
    }

    return TRUE;
}

SendMsg上のボタンを押すと3回ダイアログが表示されます. これで2つのプロセスで共有メモリを使うことが出来ました. 本来であればWaitForSingleObject()を使った排他操作をすべきですが、今回は情報で流れが一方こうなので割愛しました(という言い訳). また、ダイアログのデストラクタ( あるいはOnDestroy() )では、::UnmapViewOfFile()と::CloseHandle()を忘れずに呼び出しましょう.

参考

chokuto.ifdef.jp

yu-hr.hatenadiary.org

country-programmer.dfkp.info

www.wabiapp.com