EternalWindows
グローバルアトム / グローバルアトムテーブル

グローバルアトムを使用するアプリケーションを開発していると、 自分で追加したグローバルアトムが正しく削除されているか気になることがあります。 もし、アプリケーションがGlobalDeleteAtomを呼び出さずに終了した場合、 追加したグローバルアトムがグローバルアトムテーブルに残ってしまうことになるため、 こうした事が発生していないかを確認する方法があれば便利です。 グローバルアトムテーブルに格納されている全ての文字列を列挙すれば、 その中から自分が追加した文字列を探すようなことができますから、 このような列挙を行う方法を考えてみたいと思います。

第一感で思い浮かぶのは、グローバルアトムテーブルから文字列を列挙する関数を呼び出すことですが、 グローバルアトム系の関数の中にはこうした関数は存在しません。 しかし、文字列を表すグローバルアトムの値は0xC000から0xFFFFの範囲という法則があるため、 これを順番に加算してGlobalGetAtomNameを呼び出すようにすれば、 その値に関連する文字列を列挙できます。 これはつまり、グローバルアトムテーブルに格納された文字列を列挙するのと同じ意味を持ちます。

for (i = 0xC000; i < 0xFFFF; i++) {
	atom = (ATOM)i;
	if (GlobalGetAtomName(atom, szName, sizeof(szName) / sizeof(TCHAR)) != 0)
		MessageBox(NULL, szName, TEXT("OK"), MB_OK);
}

上記のように、0xC000からカウントするループ文を記述し、 atomの値が順に増えていくようにします。 GlobalGetAtomNameが0でない値を返したということは、 グローバルアトムに関連する文字列が存在するということですから、これを表示しています。

グローバルアトムには参照カウントというものがありますが、 これについても取得する関数は公開されていません。 しかし、非公開APIであるNtQueryInformationAtomならば、参照カウントを取得できるという情報があります。

http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Atoms/NtQueryInformationAtom.html

上記ページを参考に、NtQueryInformationAtomのプロトタイプを確認します。

NTSYSAPI 
NTSTATUS
NTAPI
NtQueryInformationAtom(
  IN RTL_ATOM             Atom,
  IN ATOM_INFORMATION_CLASS AtomInformationClass,
  OUT PVOID               AtomInformation,
  IN ULONG                AtomInformationLength,
  OUT PULONG              ReturnLength OPTIONAL );

Atomは、情報を取得したいアトムを指定します。 AtomInformationClassは、ATOM_INFORMATION_CLASS型として定義される列挙指定子を指定します。 AtomInformationは、情報を受け取るバッファを指定します。 AtomInformationClassに指定したのがAtomBasicInformationである場合は、 バッファをATOM_BASIC_INFORMATION構造体で識別でき、 AtomTableInformationである場合は、バッファをATOM_TABLE_INFORMATION構造体で識別できます。 AtomInformationLengthは、AtomInformationのサイズを指定します。 ReturnLengthは、バッファとして必要なサイズを受け取るように思えますが、そうではないようです。 試しに、AtomInformationとAtomInformationLengthにNULLと0を指定してみましたが、 ReturnLengthに必要なサイズが返ることはありませんでした。 おそらく、NULLを指定しても問題ないと思われます。

今回のプログラムは、グローバルアトムテーブルに格納されている文字列をリストボックスに列挙します。 文字列の先頭には括弧が表示されますが、これは参照カウントの値を示しています。

#include <windows.h>

typedef struct _ATOM_BASIC_INFORMATION {
	USHORT UsageCount;
	USHORT Flags;
	USHORT NameLength;
	WCHAR  Name[1];
} ATOM_BASIC_INFORMATION, *PATOM_BASIC_INFORMATION;

typedef struct _ATOM_TABLE_INFORMATION {
	ULONG NumberOfAtoms;
	ATOM  Atoms[1];
} ATOM_TABLE_INFORMATION, *PATOM_TABLE_INFORMATION;

typedef enum _ATOM_INFORMATION_CLASS {
	AtomBasicInformation,
	AtomTableInformation
} ATOM_INFORMATION_CLASS, *PATOM_INFORMATION_CLASS;

typedef NTSTATUS (NTAPI *LPFNNTQUERYINFORMATIONATOM)(ATOM, ATOM_INFORMATION_CLASS, PVOID, ULONG, PULONG);

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 hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		int                        i;
		ATOM                       atom;
		TCHAR                      szName[256], szBuf[512];
		ULONG                      uSize;
		BYTE                       buffer[1024];
		LPFNNTQUERYINFORMATIONATOM lpfnNtQueryInformationAtom;
		PATOM_BASIC_INFORMATION    pBasicInformation;

		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		lpfnNtQueryInformationAtom = (LPFNNTQUERYINFORMATIONATOM)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationAtom");

		for (i = 0xC000; i < 0xFFFF; i++) {
			atom = (ATOM)i;
			if (GlobalGetAtomName(atom, szName, sizeof(szName) / sizeof(TCHAR)) != 0) {
				uSize = sizeof(buffer);
				pBasicInformation = (PATOM_BASIC_INFORMATION)buffer;
				lpfnNtQueryInformationAtom(atom, AtomBasicInformation, pBasicInformation, uSize, NULL);

				wsprintf(szBuf, TEXT("(%d) %s"), pBasicInformation->UsageCount, szName);
				SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			}
		}
		
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

NtQueryInformationAtomに関する定義は、既存のヘッダーファイルに存在していないため、 コード上で明示的に定義するようにしています。 NtQueryInformationAtomはntdll.dllからエクスポートされており、 このDLLは必ずプロセスにロードされているため、 LoadLibrayよりGetModuleHandleを呼び出すのが簡単です。 GlobalGetAtomNameが成功したらNtQueryInformationAtomを呼び出すことができますが、 第2引数にAtomBasicInformationを指定している点に注意してください。 これにより、第3引数のバッファをATOM_BASIC_INFORMATION構造体で表すことができ、 UsageCountから参照カウントを知ることができます。 なお、AtomTableInformationはグローバルアトムを列挙したい場合に使用できますが、 GlobalGetAtomNameによる列挙の方が分かりやすいと思われます。


戻る