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

MSIファイルからデータを取得するには、まずデータベースのハンドルを取得することになります。 ここで言うデータベースとは、いわばMSIファイルそのものなのですが、 データベースという単位に見立てることで、テーブルやレコードといった概念が扱えます。 次に示すMsiOpenDatabaseは、データベースをオープンします。

UINT MsiOpenDatabase(
  LPCTSTR szDatabasePath,
  LPCTSTR szPersist,
  MSIHANDLE *phDatabase
); 

szDatabasePathは、MSIファイルが存在するファイルパスを指定します。 szPersistは、どのような目的でデータベースをオープンするかです。 型がLPTSTRとなっていますが、実は専用の定数が用意されており、 たとえばMSIDBOPEN_READONLYを指定したならば、読み取り専用でオープンされることになります。 phDatabaseは、データベースのハンドルを受け取ります。 MSIの関数では、あらゆるハンドルをこのMSIHANDLE型で識別することになります。 戻り値は、関数が成功した場合はERROR_SUCCESSが返ることになっており、 これは多くのMSIの関数において共通しています。

取得したMSIHANDLEは、不要になったらMsiCloseHandleで閉じることになります。

UINT MsiCloseHandle(
  MSIHANDLE hAny
);

hAnyは、不要になったMSIHANDLEを指定します。

リファレンスによれば、ハンドルをローカルに宣言して扱う場合は、 MSIHANDLE型ではなくPMSIHANDLE型を使うべきという記述があります。 このPMSIHANDLEは、一見するとMSIHANDLEへのポインタに見えますが 実はMSIHANDLEを継承したC++のクラスであり、デスクトラクタを利用することで 暗黙的にMSIHANDLEを閉じようという狙いです。 しかし、本章ではハンドルが不要になった時点を明確にするためにMSIHANDLE型を扱い、 MsiCloseHandleで明示的に閉じることにします。

次節で説明するように、データベースからレコードを取得するには、 ビューと呼ばれるものをオープンしなければなりませんが、 データベースのハンドルにもいくつかの用途はあります。 たとえば、MsiDatabaseIsTablePersistentという関数を呼び出せば、 指定したテーブルがデータベースに存在するかどうかを調べることができます。

MSICONDITION MsiDatabaseIsTablePersistent(
  MSIHANDLE hDatabase,
  LPCTSTR szTableName
);

hDatabaseは、データベースのハンドルを指定します。 szTableNameは、存在するかどうかを確認したいテーブルの名前を指定します。 リファレンスに記されている正規のテーブルでも、存在しないときは関数は失敗します。 関数の戻り値はMSICONDITIONというあまり利用されない型で、 MSICONDITION_TRUEが格納されたときには、テーブルが存在することを意味します。

今回のプログラムは、MsiOpenDatabaseでデータベースをオープンし、 指定したテーブルが存在するかどうかを調べます。 データベース用の関数を扱う場合にはmsiquery.hをインクルードし、 msi.libへのリンクはMSIの関数を扱う場合は必須となります。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	UINT         uResult;
	MSICONDITION condition;
	MSIHANDLE    hDatabase;
	
	uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_READONLY, &hDatabase);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), TEXT("OK"), MB_ICONWARNING);
		return 0;
	}

	condition = MsiDatabaseIsTablePersistent(hDatabase, TEXT("Control"));
	if (condition == MSICONDITION_TRUE)
		MessageBox(NULL, TEXT("テーブルは存在します。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("テーブルは存在しません。"), NULL, MB_ICONWARNING);
	
	MsiCloseHandle(hDatabase);

	return 0;
}

MsiOpenDatabaseでカレントディレクトリに用意されたsample.msiをオープンし、 MsiDatabaseIsTablePersistentでテーブルが実際に存在するかを調べます。 Controlというテーブルは大抵のMSIファイルには存在するため、 テーブルは存在するという結果が表示されると思われます。

MsiDatabaseIsTablePersistentの名前から予想がつくかもしれませんが、 MSIの関数の名前は第1引数に指定するハンドルの種類と一致するようになっています。 つまり、データベースのハンドルを指定する場合は、MsiDatabaseから始まり、 ビューやレコードの場合はそれぞれ、MsiView、MsiRecordとなります。 MSIの関数は非常に種類が多いので、この法則を覚えておくと関数の用途が分かりやすくなります。

テーブルのエクスポートとインポート

データベースの特定のテーブルをファイルに保存したいような場合、 MsiDatabaseExportによるテーブルのエクスポートが有効です。 ファイルはテキスト形式で記述されているため、内容を容易に確認できます。

TCHAR szDirectory[256];
GetCurrentDirectory(sizeof(szDirectory), szDirectory);
MsiDatabaseExport(hDatabase, TEXT("Control"), szDirectory, TEXT("data.idt"));

MsiDatabaseExportの第2引数はエクスポートしたいテーブルを、 第3引数はファイルをエクスポートするディレクトリ、第4引数は作成されるファイル名です。 この例では、GetCurrentDirectoryで取得したディレクトリを指定しているため、 カレントディレクトリにControlテーブルの中身を記述したdata.idtが作成されることになります。 idtという拡張子はOrcaを模倣しただけで、たとえば.txtという拡張子でも問題はありません。

エクスポートしたテーブルは、MsiDatabaseImportでインポートすることができます。 テーブルをインポートするとは、そのテーブルの内容を取り込むことで、 インポート側からすればテーブルの内容を置き換えたことになります。 予め、MsiDatabaseExportでテーブルのバックアップをとっておけば、 テーブルの内容を元に戻したいときにMsiDatabaseImportを呼び出すことができます。

MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_TRANSACT, &hDatabase);
GetCurrentDirectory(sizeof(szDirectory), szDirectory);
MsiDatabaseImport(hDatabase, szDirectory, TEXT("data.idt"));
MsiDatabaseCommit(hDatabase);

MsiDatabaseImportの第2引数はインポートしたいファイルが存在するディレクトリ、 第3引数はインポートするファイル名です。 data.idtがControlテーブルの中身をエクスポートしたものであれば、 hDatabaseのControlテーブルはdata.idtのものに置き換わります。 これは一種のテーブルへの書き込みであるため、MsiOpenDatabaseの第2引数に MSIDBOPEN_TRANSACTを指定し、書き込み処理の終了時にMsiDatabaseCommitを呼び出します。 これにより、書き込み結果がMSIファイルに反映されます。



戻る