EternalWindows
MSI データベース編 / ビューのオープン

テーブルに格納されたレコードを取り扱う場合は、まずどのテーブルを対象とするのかを決め、 さらにそのテーブルに何を要求するのかを決めなければなりません。 ここで言う要求とは、テーブルからレコードを取得するのか、 あるいは新しいレコードを追加するのかというような1つの命令であり、 それはSQLという言語に基づいた文字列で表されることになります。 次に示すMsiDatabaseOpenViewは、指定したSQL文を解釈できるビューというオブジェクトを作成し、 そのハンドルを返します。

UINT MsiDatabaseOpenView(
  MSIHANDLE hDatabase,
  LPCTSTR szQuery,
  MSIHANDLE *phView
);

hDatabaseは、データベースのハンドルを指定します。 szQueryは、データベースに対する問い合わせを示すSQL文を指定します。 phViewは、ビューのハンドルが返ります。

SQLは、OracleやAccessといった一般的に言われるデータベース対して用いる言語ですが、 MSIのデータベースにおいても、SQL文は用いられることになります。 SQL文は、命令と対象となるテーブルを1つまとめた文字列であり、 たとえば、次のような文を書くことができます。

SELECT ColumnName FROM TableName

この文は、TableNameで指定されたテーブルからレコードを取得するという意味で、 文頭のSELECTというキーワードがレコードの取得を意味しています。 ColumnNameは列の名前を指定し、特定の列のフィールド値のみを参照することができます。 一般のデータベースでは、命令によって文頭のキーワードを書き換え、 文の形式もそのキーワードによって異なるものですが、MSIでのプログラミングでは、 常にSELECT文を用いてもレコードの取得以外の操作を行うことができます。 これは、ビューが必要とする情報があくまでテーブル名だからであり、 MSIではそれを指定する手段としてSQL文を用いているだけですから、 キーワード自体は重要なことはないのです。

さて、ビューのハンドルを取得すれば、いよいよレコードの操作を行うことができます。 まず今回は、テーブルの列名をレコードとして返すMsiViewGetColumnInfoを基に、 レコードの操作を見ていくことにします。

UINT MsiViewGetColumnInfo(
  MSIHANDLE hView,
  MSICOLINFO eColumnInfo,
  MSIHANDLE *phRecord
);

hViewは、ビューのハンドルを指定します。 eColumnInfoは、レコードに含める情報の種類を示したフラグを指定します。 MSICOLINFO_NAMESを指定した場合は列名が格納され、 MSICOLINFO_TYPESの場合は列に書き込める最大サイズが格納されます。 phRecordは、レコードが返ります。

レコードを取得したら、次はレコードのフィールドを取得します。 処理の流れとしては、まずフィールドの数を取得し、 その数だけフィールド値を参照する場合が多いでしょう。 フィールドの数は、MsiRecordGetFieldCountで取得することができます。

unsigned int MsiRecordGetFieldCount(
  MSIHANDLE hRecord
);

hRecordは、レコードのハンドルを指定します。 戻り値がフィールドの数となります。

フィールド値は、MsiRecordGetStringによる文字列形式と、 MsiRecordGetIntegerによる数値形式による取得方法があります。 次に、MsiRecordGetStringの定義を示します。

UINT MsiRecordGetString(
  MSIHANDLE hRecord,
  unsigned int iField,
  LPTSTR szValueBuf,
  DWORD *pcchValueBuf
);

hRecordは、レコードのハンドルを指定します。 iFieldは、何番目のフィールド値を取得するのかを示すインデックスを指定します。 szValueBufは、フィールド値を受け取るバッファのアドレスを指定します。 pcchValueBufは、バッファのサイズを格納した変数のアドレスを指定します。

今回のプログラムは、MsiViewGetColumnInfoで取得したレコードのフィールド値を表示します。 つまり、特定のテーブルにおける全ての列名を表示します。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	UINT      i, uCnt;
	UINT      uResult;
	DWORD     dwSize;
	TCHAR     szBuf[256];
	MSIHANDLE hDatabase;
	MSIHANDLE hView;
	MSIHANDLE hRecord;

	uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_READONLY, &hDatabase);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	uResult = MsiDatabaseOpenView(hDatabase, TEXT("SELECT * FROM Control"), &hView);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("ビューのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		MsiCloseHandle(hDatabase);
		return 0;
	}
	
	MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hRecord);

	uCnt = MsiRecordGetFieldCount(hRecord) + 1;
	for (i = 1; i < uCnt; i++) {
		dwSize = sizeof(szBuf);
		MsiRecordGetString(hRecord, i, szBuf, &dwSize);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

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

	return 0;
}

MsiDatabaseOpenViewの第2引数であるSQL文では、 テーブル名としてControlを指定しているため、 Controlテーブルの列名が表示されることになります。

uResult = MsiDatabaseOpenView(hDatabase, TEXT("SELECT * FROM Control"), &hView);
if (uResult != ERROR_SUCCESS) {
	MessageBox(NULL, TEXT("ビューのオープンに失敗しました。"), NULL, MB_ICONWARNING);
	MsiCloseHandle(hDatabase);
	return 0;
}

SQL文の列名の部分では*を用いていますが、これは特定のフィールドのみではなく、 全てのフィールドを取得することを意味しています。 MsiDatabaseOpenViewでは、不正なSQL文の形式や存在しない列やテーブルの指定で 関数が失敗することがよくあるので、戻り値はできるだけ確認するようにしてください。 次に、MsiViewGetColumnInfoの呼び出しを見てみます。

MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hRecord);

ここでは、第2引数にMSICOLINFO_NAMESを指定しているので、 レコードの各フィールドには列名が格納されることになります。 MSICOLINFO_TYPESを指定した場合は、列に指定可能なサイズが格納され、 たとえばS20ならば最大20文字、I2なら2バイトの数値という具合になります。

レコードの各フィールドには、対応する列に関する情報が格納されますが、 先頭のフィールド(フィールドゼロ)に関しては例外です。 このフィールドに値は、関数によって内部的に扱われるものあるため、 インデックスは1から指定するようにし、フィールドの数も1つ多めにカウントします。

uCnt = MsiRecordGetFieldCount(hRecord) + 1;
for (i = 1; i < uCnt; i++) {
	dwSize = sizeof(szBuf);
	MsiRecordGetString(hRecord, i, szBuf, &dwSize);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
}

MsiViewGetColumnInfoは、一連の列を擬似的にレコードとして返す関数であるため、 当然ながら返されるレコードの数は1つのみです。 そのため、1つのループでフィールド値を取得することができます。 しかし、特定のテーブルから本当のレコードを取得する場合はその数は複数になりますから、 レコードの取得とフィールドの取得という二重ループを書くことになります。

プライマリキーの取得

テーブルにおけるカラムの属性には、名前、タイプ、キー、NULLの指定可否、という4つの 種類があり、このうち名前とタイプはMsiViewGetColumnInfoで取得することができました。 キーというのは、そのカラムがプライマリキー、もしくは外部キーの役割を担っているかどうかを 示すもので、これはMsiDatabaseGetPrimaryKeysで取得することができます。

MsiDatabaseGetPrimaryKeys(hDatabase, TEXT("Control"), &hRecord);
uCnt = MsiRecordGetFieldCount(hRecord) + 1;
for (i = 1; i < uCnt; i++) {
	dwSize = sizeof(szBuf);
	MsiRecordGetString(hRecord, i, szBuf, &dwSize);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
}

MsiDatabaseGetPrimaryKeysの第2引数は、プライマリキー及び外部キーを 検索するためのテーブルを指定し、キーとなるカラムがフィールドとして 第3引数のレコードに格納されることになります。 レコードでフィールドを走査する方法は、MsiViewGetColumnInfoと同一です。 なお、NULLを指定可能かどうかを判定する関数は存在しないため、 これに関してはリファレンスを参考するしか方法はないと思われます。 MsiRecordIsNullという関数がありますが、この関数はレコードの特定のフィールドが 現在NULLに設定されているかを調べるだけとなっています。



戻る