EternalWindows
ウインドウ管理 / プロセス間通信

これまで、メッセージ送信の方法としてPostMessageとSendMessageの2種類が あることを説明しましたが、実はこの両方の関数には、 他のプロセスが作成したウインドウのハンドルを指定することもできます。 これはつまり、他のウインドウに自分が送りたいメッセージを送れるということなので、 メッセージを通じて対象となるウインドウを制御することや、データを送ることができます。 問題は、対象となるウインドウのハンドルをどうやって取得するかですが、 これはFindWindowを呼び出すのが最も簡単であると思われます。

FindWindowの引数から分かることですが、あるウインドウのハンドルを取得するには、 そのウインドウについての情報をある程度知っておかなければなりません。 たとえば、メモ帳のウインドウハンドルを取得しようと考えた場合、 ウインドウタイトルは開いているテキストによって変化しますし、 クラス名については何処にも表示されていませんから、 FindWinodwに指定する引数は一般には悩まされるものです。 しかし、幸いにもどちらか片方の引数にはNULLを指定できるので、 次のような呼び出し方が可能となります。

hwndTarget = FindWindow(TEXT("Notepad"), NULL);
if (hwndTarget != NULL)
	PostMessage(hwndTarget, WM_CLOSE, 0, 0);

このコードは、クラス名のみを指定してウインドウを探そうとしています。 そして、指定したクラス名を持つウインドウのハンドルを取得できたら、 そのウインドウ宛にWM_CLOSEをポストして、ウインドウをクローズさせています。 ちなみに、「Notepad」という文字列はメモ帳のクラス名です。 何故メモ帳のクラス名が分かったのかは、次節で説明します。

FindWindowで厄介となるのは、クラス名やウインドウタイトルの取得ですが、 そのウインドウが自作したプロセスによって作られているものであれば、 クラス名やウインドウタイトルはプログラムの設計時に把握していますから、 FindWindowの呼び出しに困ることはないはずです。 なにより、自作したプロセス間の通信は互いの仕様を理解していることから、 メッセージを通じてのデータ交換が可能となるという大きな利点があります。 このようなデータ交換には、WM_COPYDATAというメッセージが役に立ちます。

SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);

WM_COPYDATAは、必ずSendMessageで送ります。 wParamは、送信元のウインドウハンドルを指定します。 lParamは、COPYDATASTRUCT構造体のアドレスです。 COPYDATASTRUCT構造体は、以下のように定義されています。

typedef struct tagCOPYDATASTRUCT {
    ULONG_PTR dwData;
    DWORD cbData;
    PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

dwDataは、送るべきデータの識別子を指定します。 WM_COPYDATAを受け取った側はこの値を参照して、 どの系統のデータが送られてきたのかを確認できます。 cbDataは、lpDataが指すメモリのサイズです。 COPYDATASTRUCT構造体のサイズを指定するのではありません。 lpDataは、送りたいデータを格納するメモリへのアドレスを指定します。 このメンバには、NULLを指定することもできます。

今回のプログラムは、次節で作成するウインドウにWM_COPYDATAを送ります。 FindWindowの呼び出しはWM_CREATEで一度行うだけですから、 事前に次節のプログラムを起動しておいてください。

#include <windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndTarget = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hwndTarget = FindWindow(TEXT("receiver"), NULL);
		if (hwndTarget == NULL) {
			MessageBox(NULL, TEXT("指定したウインドウを見つけることができませんでした。"), NULL, MB_ICONWARNING);
			return -1;
		}
		return 0;

	case WM_LBUTTONDOWN: {
		TCHAR          szData[] = TEXT("hello");
		COPYDATASTRUCT data;

		data.dwData = 1;
		data.cbData = sizeof(szData);
		data.lpData = szData;
		
		SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);

		return 0;
	}
	
	case WM_RBUTTONDOWN: {
		COPYDATASTRUCT data;

		data.dwData = 2;
		data.cbData = 0;
		data.lpData = NULL;
		
		SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_COPYDATAは、マウスの左ボタンと右ボタンの両方で送ることになります。 その時々でFindWindowを呼び出すのは煩わしいため、 WM_CREATEで予め取得するようにしています。

hwndTarget = FindWindow(TEXT("receiver"), NULL);

第1引数には、メッセージの送信先となるウインドウのクラス名を指定します。 receiverという文字列は、次節で作成するウインドウのクラス名です。

case WM_LBUTTONDOWN: {
	TCHAR          szData[] = TEXT("hello");
	COPYDATASTRUCT data;

	data.dwData = 1;
	data.cbData = sizeof(szData);
	data.lpData = szData;
	
	SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);

	return 0;
}

COPYDATASTRUCT構造体を初期化し、lpDataに文字列のアドレスを指定します。 次節のプログラムは、この文字列を受け取って表示します。

case WM_RBUTTONDOWN: {
	COPYDATASTRUCT data;

	data.dwData = 2;
	data.cbData = 0;
	data.lpData = NULL;
	
	SendMessage(hwndTarget, WM_COPYDATA, (WPARAM)hwnd, (LPARAM)&data);

	return 0;
}

こちらのWM_COPYDATAではlpDataをNULLにしており、 データを何も送っていないわけですが、これはこれで意味があります。 WM_COPYDATAを送るということは、 それだけで何らかの処理の要求やデータの変更の通知などという意味を込めることができるので、 送信側がデータを用意する義務はないのです。 ただ、このdwDataは他のWM_COPYDATAのdwDataと決して重複してはなりません。 dwDataは、送ってきたデータがどのようなものかを識別するものですから、 ここでdwDataを1にしたとなると、受信側は1の場合を文字列の受信と解釈していますから、 NULLの文字列を処理させてしまうことになります。

メッセージの範囲

メッセージを通じて何らかの通知や要求を行いたい場合、 WM_COPYDATAを利用するのが必ずしも効果的とは限りません。 どのようなメッセージであれ、それを当事者間が通信の合図と認識できればよいのですから、 独自に定義した数値をメッセージとして利用するのもまた一興です。 ただし、このような場合、その定義した数値が既存のメッセージと重複してはなりませんから、 予め利用可能なメッセージの範囲を知っておかなければなりません。

メッセージの範囲 意味
0からWM_USER - 1 システムによって定義されているメッセージ。
WM_USERから0x7FFF 個々のウインドウクラスのためのメッセージ。 たとえば、ツールチップコントロールをアクティブにするTTM_ACTIVATEは、 WM_USER + 1として定義されている。
WM_APPから0xBFFF アプリケーションが自由に使ってよいとされる範囲。
0xC000から0xFFFF RegisterWindowMessageが返す値。
0xFFFFより大きい値 将来のために予約されている。

注目すべきところはWM_APPです。 このメッセージから0xBFFFまでの値はアプリケーションが自由に使うことが許されているため、 オリジナルなメッセージとしてアプリケーション間の通信に利用できます。 WM_COPYDATAはデータの送信を目的としたメッセージですが、 単純に通知や要求などを行いたい場合はWM_APPのほうが適切でしょう。

#define WM_LAYOUTCHANGE WM_APP
・
・
・
PostMessage(hwndTarget, WM_LAYOUTCHANGE, 0, 0); // SendMessageでもよい

このコードでは、WM_APPを意図的にWM_LAYOUTCHANGEという形で定義していますが、 これはメッセージの役割を名前から推測できるようにするためです。 たとえば、対象となるウインドウにレイアウトの変更を促すのであれば、 それに即した名前を使ったほうがコードは分かりやすくなります。 wParamとlParamは自由に使うことができますが、 あくまで送れるのは4バイトのデータが2つまでとなります。 複数のオリジナルメッセージが必要な場合は、WM_APP+n(nは0から0xBFFF)という形で定義します。



戻る