EternalWindows
MSI データベース編 / レコードの取得

MsiDatabaseOpenViewによって作成されたビューは、対象とするテーブルを把握しているものの、 指定されたSQL文自体は実行されていません。 SQL文を実行するには、MsiViewExecuteを呼び出します。

UINT MsiViewExecute(
  MSIHANDLE hView,
  MSIHANDLE hRecord
);

hViewは、ビューのハンドルを指定します。 hRecordは、オプションの引数であるため、0を指定して問題はありません。

MsiViewExecuteを呼び出すと、SQL文に指定した内容が実際に実行されます。 たとえば、SELECT文ならば、指定したテーブルに格納されているレコードが 結果セット(一連のレコード)として作成されます。 この結果セットの個々のレコードは、MsiViewFetchで取り出すことができます。

UINT MsiViewFetch(
  MSIHANDLE hView,
  MSIHANDLE *phRecord
);

hViewは、ビューのハンドルを指定します。 phRecordは、取り出したレコードのハンドルが格納されます。 基本的にこの関数は、戻り値としてERROR_NO_MORE_ITEMSが返るまで繰り返し呼び出しますが、 ERROR_SUCCESSを判定に使った方が、関数の失敗時に対応しやすくなります。 次に、MsiViewFetchを呼び出すループ文の例を示します。

for (;;) {
	uResult = MsiViewFetch(hView, &hRecord);
	if (uResult != ERROR_SUCCESS)
		break;

	// フィールドを走査する

	MsiCloseHandle(hRecord);
}

ERROR_SUCCESSでない値が返ったときは、不正な引数によるエラーであっても、 取り出すべきレコードがないためであっても、これ以上ループを実行する必要はありませんから、 ループから抜けるようにします。 不要になったレコードは、MsiCloseHandleで閉じておきます。

今回のプログラムは、Orcaのようなテーブルのレコードを示すリストビューを作成します。 リストビューのカラムは前節で説明したMsiViewGetColumnInfoで初期化し、 テーブルに含まれるレコードはMsiViewFetchで取得します。

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

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

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;

	switch (uMsg) {

	case WM_CREATE: {
		UINT                 i, j;
		UINT                 uCnt;
		UINT                 uResult;
		DWORD                dwSize;
		TCHAR                szBuf[256];
		MSIHANDLE            hDatabase;
		MSIHANDLE            hView;
		MSIHANDLE            hRecord;
		LVITEM               item;
		LVCOLUMN             column;
		INITCOMMONCONTROLSEX ic;
		
		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);
	
		uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_READONLY, &hDatabase);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		uResult = MsiDatabaseOpenView(hDatabase, TEXT("SELECT * FROM Dialog"), &hView);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("ビューのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			MsiCloseHandle(hDatabase);
			return -1;
		}
		
		MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hRecord);

		uCnt = MsiRecordGetFieldCount(hRecord) + 1;
		for (i = 1; i < uCnt; i++) {
			dwSize = sizeof(szBuf);
			MsiRecordGetString(hRecord, i, szBuf, &dwSize);

			column.mask    = LVCF_WIDTH | LVCF_TEXT;
			column.cx      = 100;
			column.pszText = szBuf;
			ListView_InsertColumn(hwndListView, i - 1, &column);
		}
		MsiCloseHandle(hRecord);

		MsiViewExecute(hView, 0);

		for (i = 0;; i++) {
			uResult = MsiViewFetch(hView, &hRecord);
			if (uResult != ERROR_SUCCESS)
				break;
			
			uCnt = MsiRecordGetFieldCount(hRecord) + 1;
			for (j = 1; j < uCnt; j++) {
				dwSize = sizeof(szBuf);
				MsiRecordGetString(hRecord, j, szBuf, &dwSize);

				item.mask     = LVIF_TEXT;
				item.iItem    = i;
				item.iSubItem = j - 1;
				item.pszText  = szBuf;
				if (j - 1 == 0)
					ListView_InsertItem(hwndListView, &item);
				else
					ListView_SetItem(hwndListView, &item);
			}

			MsiCloseHandle(hRecord);
		}
		
		MsiCloseHandle(hView);
		MsiCloseHandle(hDatabase);

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

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

処理の内容は、MsiViewGetColumnInfoで取得したレコードを扱うコードとMsiViewFetchで 取得したレコードを扱うコードの2つに分かれます。 前者のコードは、次の部分です。

MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hRecord);

uCnt = MsiRecordGetFieldCount(hRecord) + 1;
for (i = 1; i < uCnt; i++) {
	dwSize = sizeof(szBuf);
	MsiRecordGetString(hRecord, i, szBuf, &dwSize);

	column.mask    = LVCF_WIDTH | LVCF_TEXT;
	column.cx      = 100;
	column.pszText = szBuf;
	ListView_InsertColumn(hwndListView, i - 1, &column);
}
MsiCloseHandle(hRecord);

基本的にこのコードは、リストビューに関わるコード以外は前節で示したものと同一です。 リストビューでカラムを扱うにはListView_InsertColumnを使用し、 第2引数のインデックスはゼロベースであるため、1から始まるループでは-1することになります。 columnはLVCOLUMN構造体で、cxメンバとpszTextメンバを扱う場合は、 maskメンバにLVCF_WIDTHとLVCF_TEXTを指定します。 続いて、MsiViewFetchに関わるコードについて見てみます。

MsiViewExecute(hView, 0);

for (i = 0;; i++) {
	uResult = MsiViewFetch(hView, &hRecord);
	if (uResult != ERROR_SUCCESS)
		break;
	
	uCnt = MsiRecordGetFieldCount(hRecord) + 1;
	for (j = 1; j < uCnt; j++) {
		dwSize = sizeof(szBuf);
		MsiRecordGetString(hRecord, j, szBuf, &dwSize);

		item.mask     = LVIF_TEXT;
		item.iItem    = i;
		item.iSubItem = j - 1;
		item.pszText  = szBuf;
		if (j - 1 == 0)
			ListView_InsertItem(hwndListView, &item);
		else
			ListView_SetItem(hwndListView, &item);
	}

	MsiCloseHandle(hRecord);
}

まず、MsiViewFetchの呼び出しの前にMsiViewExecuteでSQL文を実行しておきます。 フィールドの走査では、MsiViewGetColumnInfoと同じようなループ文を記述し、 取得したフィールド値をリストビューのアイテムとして追加します。 注意すべきところは、先頭のアイテムにはListView_InsertItemを用いるのに対して、 2番目以降のサブアイテムにはListView_SetItemを用いるところと、 アイテムのインデックスはいずれもゼロベースで始めるため、 フィールドの添字に使っているjは-1する必要があるという点です。


戻る