EternalWindows
WMI / パフォーマンスカウンタの使用

WMIの利点として、様々な情報へ統一的な方法でアクセスできるというものがありました。 つまり、パフォーマンスカウンタならばPDH API、イベントログならばEvent Logging APIという要領ではなく、 どちらの情報に対してでもWMIのインターフェースでアクセスできるということですが、 具体的にはどういうコードを記述すればよいのでしょうか。 これまで見てきたように、WMIはクラスという存在をベースにしており、 その中にはパフォーマンスカウンタを定義するクラスも存在しています。 たとえば、PDHにはProcessというカウンタがありましたが、 WMIではこれがWin32_PerfRawData_PerfProc_Processに相当します。

パフォーマンスカウンタクラスへ高速にアクセスする手段として、 WMI high-performance APIが用意されています。 このAPIを使用するには、CoCreateInstanceにCLSID_WbemLocatorを指定してRefresherというオブジェクトを作成します。 RefresherはIWbemRefresherで表すことができますが、 このインターフェースには列挙したいパフォーマンスクラスを指定するメソッドがないため、 別途IWbemConfigureRefresherを照会することになるでしょう。 そして、AddEnumを呼び出します。

HRESULT IWbemConfigureRefresher::AddEnum(
  IWbemServices *pNamespace,
  LPCWSTR wszClassName,
  long lFlags,
  IWbemContext *pCtx,
  IWbemHiPerfEnum **ppEnum,
  long *plId
);

pNamespaceは、IWbemServices型の変数を指定します。 wszClassNameは、列挙したいクラスの名前を指定します。 lFlagsは、メソッドの動作を修正するフラグを指定します。 pCtxは、NULLで問題ありません。 ppEnumは、IWbemHiPerfEnumを受け取る変数のアドレスを指定します。 plIdは、列挙を識別する数値を受け取る変数のアドレスを指定します。

AddEnumが成功した場合は、Refresherの中にEnumeratorが追加されたことになります。 この状態で、IWbemRefresher::Refreshを呼び出せばEnumeratorが更新され、 IWbemHiPerfEnum::GetObjectsでインスタンスを列挙できるようになります。

HRESULT IWbemHiPerfEnum::GetObjects(
  long lFlags,
  ULONG uNumObjects,
  IWbemObjectAccess *apObj,
  ULONG *puReturned
);

lFlagsは、0で問題ないと思われます。 uNumObjectsは、apObjの要素数を指定します。 apObjは、オブジェクトの配列を受け取るバッファを指定します。 IWbemObjectAccessはプロパティに直接アクセスできるとされており、 プロパティの設定もサポートするとされています。 puReturnedは、オブジェクトの数を受け取る変数のアドレスを指定します。

今回のプログラムは、現在実行されているプロセスの名前と、そのプロセスのプライベート ワーキングセットの値をリストビューに表示します。

#include <windows.h>
#include <wbemidl.h>
#include <commctrl.h>

#define ID_UPDATE 10

#pragma comment (lib, "wbemuuid.lib")
#pragma comment (lib, "comctl32.lib")

void AddItems(HWND hwndListView, IWbemHiPerfEnum *pEnum);
void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
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            hwndListView = NULL;
	static IWbemServices   *pNamespace = NULL;
	static IWbemRefresher  *pRefresher = NULL;
	static IWbemHiPerfEnum *pEnum = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		IWbemLocator            *pLocator;
		IWbemConfigureRefresher *pConfig;
		BSTR                    bstrNamespace;
		HRESULT                 hr;
		HMENU                   hmenu;
		LVCOLUMN                column;
		INITCOMMONCONTROLSEX    ic;
		
		CoInitializeEx(0, COINIT_MULTITHREADED);
		CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

		CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pLocator));
	
		bstrNamespace = SysAllocString(L"root\\cimv2");
		hr = pLocator->ConnectServer(bstrNamespace, NULL, NULL, NULL, 0, NULL, NULL, &pNamespace);
		if (FAILED(hr)) {
			SysFreeString(bstrNamespace);
			pLocator->Release();
			return -1;
		}

		hr = CoSetProxyBlanket(pNamespace, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

		CoCreateInstance(CLSID_WbemRefresher, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pRefresher));
		pRefresher->QueryInterface(IID_PPV_ARGS(&pConfig));
		pConfig->AddEnum(pNamespace, L"Win32_PerfRawData_PerfProc_Process", 0, NULL, &pEnum, NULL);
		pConfig->Release();

		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_LISTVIEW_CLASSES;
		InitCommonControlsEx(&ic);

		hwndListView = CreateWindowEx(0, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 200;
		column.pszText = TEXT("プロセス名");
		ListView_InsertColumn(hwndListView, 0, &column);

		column.pszText = TEXT("プライベート ワーキングセット");
		ListView_InsertColumn(hwndListView, 1, &column);

		hmenu = CreateMenu();
		InitializeMenuItem(hmenu, TEXT("更新"), ID_UPDATE);
		SetMenu(hwnd, hmenu);

		pRefresher->Refresh(0);
		AddItems(hwndListView, pEnum);

		return 0;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_UPDATE) {
			pRefresher->Refresh(0);
			ListView_DeleteAllItems(hwndListView);
			AddItems(hwndListView, pEnum);
		}
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (pEnum != NULL)
			pEnum->Release();
		if (pRefresher != NULL)
			pRefresher->Release();
		if (pNamespace != NULL)
			pNamespace->Release();
		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

void AddItems(HWND hwndListView, IWbemHiPerfEnum *pEnum)
{
	ULONG             i, uCount;
	IWbemObjectAccess **pAccess;
	LONG              lHandle1, lHandle2;
	DWORD             dwSize;
	WCHAR             szName[256], szSize[256];
	LONG              lSize;
	CIMTYPE           type;
	LVITEM            item;

	pEnum->GetObjects(0, 0, NULL, &uCount);
	if (uCount <= 0)
		return;
	pAccess = (IWbemObjectAccess **)HeapAlloc(GetProcessHeap(), 0, sizeof(IWbemObjectAccess) * uCount);
	pEnum->GetObjects(0, uCount, pAccess, &uCount);

	pAccess[0]->GetPropertyHandle(L"Name", &type, &lHandle1);
	pAccess[0]->GetPropertyHandle(L"WorkingSetPrivate", &type, &lHandle2);

	for (i = 0; i < uCount; i++) {
		pAccess[i]->ReadPropertyValue(lHandle1, sizeof(szName), &lSize, (BYTE *)szName);
		pAccess[i]->ReadDWORD(lHandle2, &dwSize);
		pAccess[i]->Release();
		
		wsprintfW(szSize, L"%d K", dwSize / 1024);

		item.mask     = LVIF_TEXT;
		item.iItem    = i;
		item.iSubItem = 0;
		item.pszText  = szName;
		ListView_InsertItem(hwndListView, &item);
		
		item.iSubItem = 1;
		item.pszText  = szSize;
		ListView_SetItem(hwndListView, &item);
	}

	HeapFree(GetProcessHeap(), 0, pAccess);
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

CoCreateInstanceにCLSID_WbemRefresherを指定すれば、IWbemRefresherを取得できます。 これはWM_CREATE以外の場所でも必要になるため、静的な変数で取得しています。 QueryInterfaceでIWbemConfigureRefresherを取得しているのは、 AddEnumのようなメソッドがIWbemRefresherに含まれていないからです。 AddEnumに"Win32_PerfRawData_PerfProc_Process"を指定していることから、 プロセスのパフォーマンスカウンタが対象になります。 自作関数のAddItemsを呼び出せば実際にインスタンスが列挙されますが、 その前にIWbemRefresher::Refreshを呼び出すようにします。

void AddItems(HWND hwndListView, IWbemHiPerfEnum *pEnum)
{
	ULONG             i, uCount;
	IWbemObjectAccess **pAccess;
	LONG              lHandle1, lHandle2;
	DWORD             dwSize;
	WCHAR             szName[256], szSize[256];
	LONG              lSize;
	CIMTYPE           type;
	LVITEM            item;

	pEnum->GetObjects(0, 0, NULL, &uCount);
	if (uCount <= 0)
		return;
	pAccess = (IWbemObjectAccess **)HeapAlloc(GetProcessHeap(), 0, sizeof(IWbemObjectAccess) * uCount);
	pEnum->GetObjects(0, uCount, pAccess, &uCount);

	pAccess[0]->GetPropertyHandle(L"Name", &type, &lHandle1);
	pAccess[0]->GetPropertyHandle(L"WorkingSetPrivate", &type, &lHandle2);

	for (i = 0; i < uCount; i++) {
		pAccess[i]->ReadPropertyValue(lHandle1, sizeof(szName), &lSize, (BYTE *)szName);
		pAccess[i]->ReadDWORD(lHandle2, &dwSize);
		pAccess[i]->Release();
		
		wsprintfW(szSize, L"%d K", dwSize / 1024);

		item.mask     = LVIF_TEXT;
		item.iItem    = i;
		item.iSubItem = 0;
		item.pszText  = szName;
		ListView_InsertItem(hwndListView, &item);
		
		item.iSubItem = 1;
		item.pszText  = szSize;
		ListView_SetItem(hwndListView, &item);
	}

	HeapFree(GetProcessHeap(), 0, pAccess);
}

1回目のGetObjectsの呼び出しでは、インスタンスの数を取得することに専念します。 インスタンスの数を考慮してバッファを確保したら、 2回目の呼び出しで実際にインスタンスの配列を取得します。 後はインスタンスからプロパティを取得すればよいのですが、 このためには事前にプロパティを表すハンドルを取得しておかなければなりません。 今回のプロパティには、名前を表すNameと、プライベート ワーキングセットを表すWorkingSetPrivateがあるため、 それらのハンドルをGetPropertyHandleで取得しています。 ループ文では、実際に各インスタンスのプロパティを取得します。 可変長のプロパティはReadPropertyValueで取得しますが、 DWORDのデータはReadDWORDで取得する方が簡単です。

メニューから「更新」が選択された場合は、IWbemRefresher::Refreshを呼び出すことで、 Refresherに追加されたEnumeratorを更新します。 これにより、IWbemHiPerfEnum::GetObjectsの結果が最新のものになります。


戻る