EternalWindows
グローバルアトム / グローバルアトムと文字列

文字列という可変長のデータを、アプリケーション間で受け渡すのは難しいものがあります。 WM_APPのようなオリジナルメッセージでは、PostMessageやSendMessageに数値しか指定することができませんから、 文字列を渡すためにはWM_COPYDATAなどの別の仕組みが必要です。 ただし、文字列を直接渡すという設計から、文字列を識別する値を渡すという設計に変えれば、 相手側はその値を基に文字列を取得できるのではないでしょうか。 このためには、その値がグローバルに意味を持つものでなければなりませんが、 ここで役に立つのがグローバルアトムです。 システム(正確にはウインドウステーション)にはグローバルアトムテーブルというものが存在し、 ここにはどのようなアプリケーションでも任意の文字列を格納することが許されています。 グローバルアトムとは文字列を格納した際に返される一意の値であり、 これがあればいつでも関連する文字列を取得できます。 つまり、グローバルアトムを別のアプリケーションに送ることで、 間接的に文字列の受け渡しを再現できるようになります。

グローバルアトムを必要とするアプリケーションは、最初にGlobalAddAtomを呼び出すことになります。 この関数は指定した文字列をグローバルアトムテーブルに格納し、 その文字列を識別する値(グローバルアトム)を返します。

ATOM WINAPI GlobalAddAtom(
  LPCTSTR lpString
);

lpStringは、グローバルアトムテーブルに格納する文字列を指定します。 戻り値は、この格納した文字列を識別するグローバルアトムになります。

アトムには、文字列アトムと整数アトムの2種類が存在します。 文字列アトムはGlobalAddAtomに任意の文字列を指定した場合に作成され、 アトムの値は0xC000から0xFFFFの間になります。 また、文字列内の大文字と小文字は区別されず、後述するように参照カウントを持ちます。 一方、整数アトムは#1234のような文字列を指定した場合に作成され、 アトムの値は0x0001から0xBFFFの間になります。 #は整数アトムを識別するプレフィックスの役割を果たし、 その後には任意の数値を指定します。 基本的に整数アトムは使用しないと思われるため、文字列アトムを例に話を進めていきます。

グローバルアトムテーブルに格納された文字列は、 アプリケーションが終了しても自動で削除されるようなことはありません。 文字列を削除するには、GlobalDeleteAtomを呼び出します。

ATOM WINAPI GlobalDeleteAtom(
  ATOM nAtom
);

nAtomは、削除したい文字列に関連するグローバルアトムを指定します。

グローバルアトムはグローバルに有効な値であるため、 これは他のアプリケーションに渡すことができます。 そして、これを受け取ったアプリケーションは、 GlobalGetAtomNameで関連する文字列を取得できます。

UINT WINAPI GlobalGetAtomName(
  ATOM nAtom,
  LPTSTR lpBuffer,
  int nSize
);

nAtomは、グローバルアトムを指定します。 lpBufferは、グローバルアトムに関連する文字列を受け取るバッファを指定します。 nSizeは、lpBufferのサイズを指定します。 関数が成功した場合は、0でない値が返ります。

グローバルアトムから文字列を取得する方法があるならば、 文字列からグローバルアトムを取得する方法もあるはずです。 これには、GlobalFindAtomを呼び出します。

ATOM WINAPI GlobalFindAtom(
  LPCTSTR lpString
);

lpStringは、グローバルアトムテーブルから検索したい文字列を指定します。 指定した文字列が見つかった場合は、文字列に関連するグローバルアトムが返ります。

グローバルアトムテーブルに格納された文字列は、他のアプリケーションから取得することができるため、 一見すると二重起動の防止に役立てるように思えます。 つまり、グローバルアトムテーブルに特定の文字列が格納されていれば、 アプリケーションが実行中であると見なすわけです。 しかし、これは絶対にしてはならない使い方です。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR szAtomName[] = TEXT("InstanceCheck");
	ATOM  atom;

	if (GlobalFindAtom(szAtomName) != 0) {
		MessageBox(NULL, TEXT("二重起動が発生しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	atom = GlobalAddAtom(szAtomName);

	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	GlobalDeleteAtom(atom);

	return 0;
}

このアプリケーションを初めて実行した際には、GlobalFindAtomがszAtomNameの文字列を検索できないため、 アプリケーションは処理を続行することになります。 GlobalAddAtomを呼び出して起動中を示す文字列を格納したら、アプリケーションは何らかの操作を行いますが、 この間に再びアプリケーションを起動しようとすると、二重起動によってアプリケーションは終了することになります。 理由は、GlobalFindAtomが文字列の検索に成功するからです。 この動作は開発者の意図した通りであるといえますが、 もしこのアプリケーションがメッセージボックスを表示している間に、 アプリケーションをタスクマネージャなどで強制終了したらどうなるでしょうか。 この場合は、アプリケーションが存在しないにも関わらず、GlobalFindAtomは文字列の検索に成功することになります。 理由は、強制終了によってアプリケーションがGlobalDeleteAtomを呼び出すことができず、 グローバルアトムテーブルに文字列が残ってしまったからです。 こうした事から分かるように、グローバルアトムテーブルは二重起動の防止に使用することはできません。

GlobalDeleteAtomを呼び出しても必ず文字列が削除されるとは限りません。 グローバルアトムは参照カウントというものを持っており、 GlobalDeleteAtomはこの参照カウントを1つ下げます。 これにより参照カウントが0になった場合は、グローバルアトムに関連する文字列が削除されます。 GlobalAddAtomを呼び出した時点で、グローバルアトムの参照カウントは1になりますが、 同じ文字列をもう一度GlobalAddAtomに指定した場合は、 同じ値が返ると共に参照カウントが1つ増えることになります。 この場合は、グローバルアトムの参照カウントが2になっていますから、 GlobalDeleteAtomも2回呼び出すことになります。 つまり、GlobalDeleteAtomは、GlobalAddAtomを呼び出した回数だけ呼ばなければなりません。

次のコードは、グローバルアトムテーブルに文字列を格納し、 それに関連するグローバルアトムを自作のアプリケーションに送ります。

#include <windows.h>

#define WM_ATOM WM_APP

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 ATOM atom;

	switch (uMsg) {

	case WM_CREATE:
		atom = GlobalAddAtom(TEXT("sample-string"));
		return 0;

	case WM_LBUTTONDOWN: {
		HWND hwndTarget;
		
		hwndTarget = FindWindow(TEXT("receiver"), NULL);
		if (hwndTarget == NULL) {
			MessageBox(NULL, TEXT("指定したウインドウクラスのウインドウが存在しません。"), NULL, MB_ICONWARNING);
			return 0;
		}

		PostMessage(hwndTarget, WM_ATOM, 0, atom);

		return 0;
	}

	case WM_DESTROY:
		GlobalDeleteAtom(atom);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_CREATEで作成したグローバルアトムは、マウスの左ボタンが押された場合にアプリケーションへ送信されます。 このアプリケーションが作成するウインドウのウインドウクラスは、receiverという文字列であると仮定します。 WM_ATOMはWM_APPを基に定義したオリジナルメッセージであり、LPARAMにグローバルアトムを指定するものと仮定します。 通信相手のコード例は次のようになります。

#include <windows.h>

#define WM_ATOM WM_APP

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("receiver");
	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)
{
	switch (uMsg) {

	case WM_ATOM: {
		ATOM  atom = (ATOM)lParam;
		TCHAR szBuf[256];

		GlobalGetAtomName(atom, szBuf, sizeof(szBuf) / sizeof(TCHAR));
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

		return 0;
	}

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_ATOMが渡されたら、LPARAMからグローバルアトムを取り出してGlobalGetAtomNameを呼び出します。 そうすると、グローバルアトムに関連する文字列を取得できます。 つまり、通信元で定義されたsample-stringという文字列が、通信先に渡ったということになります。


戻る