EternalWindows
Cabinet API / 階層構造とファイルの情報

今回は、キャビネットの中でファイルがどのように管理されているのかを見ていきましょう。 まずは、ファイルに階層構造を持たせる方法についてです。 結論から述べると、キャビネットにはファイルシステムで言うところのディレクトリというものは存在しません。 つまり、階層構造を持たせるためにキャビネットの中に、 ファイルの格納場所を作成するようなことはありません。 追加するのはあくまでファイルであり、 このファイル名に階層上の位置を付け加えることによって、 展開処理を行うアプリケーションは、ディレクトリを作成してくれるようになります。

具体的な例で考えてみましょう。 次のコードは前節で示したAddFileの内部で、lpszFilePath[i]はc:\sample.txtと仮定します。

lpszFileName = PathFindFileNameA(lpszFilePath[i]);
bResult = FCIAddFile(hfci, lpszFilePath[i], lpszFileName, FALSE, NULL, StatusProc, GetOpenInfoProc, tcomp);

PathFindFileNameで得られるアドレスは、ファイルパスのファイル名の部分であるため、 この例の場合であれば、lpszFileNameは"sample.txt"となります。 このファイル名に階層上の位置が加わるというのは、たとえば、次のようになります。

"file\\sample.txt"

このような文字列をファイル名としてFCIAddFileに指定した場合、 正にこの通りの文字列がキャビネットに含まれることになります。 展開処理を行うアプリケーションもこの通りのファイル名を取得することになるため、 fileというディレクトリの下にsample.txtを作成すればよいものと解釈できることになります。

続いて、キャビネット上におけるファイルの情報について見ていきます。 ディスク上におけるファイルシステムのファイルは、 名前以外に作成日時や隠しファイルかどうかなどの属性を持っていますが、 このような情報はキャビネット上のファイルにも含まれることになります。 ただし、それらの情報はキャビネット上のファイルに対してはMS-DOS形式で指定しなければならず、 FCIAddFileはここまでの動作は行いません。 代わりにFCIAddFileはPFNFCIGETOPENINFO型のコールバック関数を呼び出すことによって、 MS-DOS形式の情報をアプリケーションから取得しようとします。 次のコードは、前節で示したGetOpenInfoProcの内部です。

int DIAMONDAPI GetOpenInfoProc(char *pszName, USHORT *pdate, USHORT *ptime, USHORT *pattribs, int *err, void *pv)
{
	HANDLE   hFile;
	DWORD    dwDosFileAttributes;
	FILETIME fileTime;
	FILETIME localFileTime;

	hFile = CreateFileA(pszName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return -1;

	GetFileTime(hFile, NULL, NULL, &fileTime);
	FileTimeToLocalFileTime(&fileTime, &localFileTime);
	FileTimeToDosDateTime(&localFileTime, pdate, ptime);

	dwDosFileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE;
	*pattribs = (USHORT)(GetFileAttributesA(pszName) & dwDosFileAttributes);

	return (int)hFile;
}

このコールバック関数は、FCIAddFileが自身の第2引数で指定したファイルをオープンする という目的に加えて、先に述べたMS-DOS形式の情報を取得しようとしています。 その情報を表す変数がpdate、ptime、pattribsで、それぞれ日付、時刻、ファイル属性となります。 まず、GetFileTimeで取得した時間をFileTimeToLocalFileTimeでローカル時間に変換します。 これにより、FileTimeToDosDateTimeでMS-DOS形式の日付と時刻が取得できます。 ファイル属性については、MS-DOS形式でサポートされているものには限りがあるため、 まずはそれら属性を表す定数をdwDosFileAttributesにまとめ、 それとGetFileAttributesが返すファイル属性と論理積をとれば、 dwDosFileAttributesが表す属性のいずれかのみが有効になります。 なお、このようにファイルの時間と属性の両方が取得になるような場合は、 GetFileInformationByHandleを呼び出すのも候補といえます。 この関数が返すBY_HANDLE_FILE_INFORMATION構造体にはそれらの情報が含まれています。

今回のプログラムは、ファイルが階層構造を持つように設計しています。 GetOpenInfoProcの内部は、前節のものと同一です。

#include <windows.h>
#include <shlwapi.h>
#include <fci.h>

#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "cabinet.lib")

BOOL StartFci(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, LPSTR *lpszAddFilePath, LPSTR *lpszAddDirectryPath, int nFileCount, TCOMP tcomp);
HFCI CreateFciContext(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, PERF perf);
BOOL AddFile(HFCI hfci, LPSTR *lpszFilePath, LPSTR *lpszDirectryPath, int nFileCount, TCOMP tcomp);

int DIAMONDAPI GetOpenInfoProc(char *pszName, USHORT *pdate, USHORT *ptime, USHORT *pattribs, int *err, void *pv);
long DIAMONDAPI StatusProc(UINT typeStatus, ULONG cb1, ULONG cb2, void *pv);
int DIAMONDAPI FilePlaceProc(PCCAB pccab, char *pszFile, long cbFile, BOOL fContinuation, void *pv);

void * DIAMONDAPI AllocProc(ULONG cb);
void DIAMONDAPI FreeProc(void *memory);
int DIAMONDAPI OpenProc(char *pszFile, int oflag, int pmode, int *err, void *pv);
UINT DIAMONDAPI ReadProc(int hf, void *memory, UINT cb, int *err, void *pv);
UINT DIAMONDAPI WriteProc(int hf, void *memory, UINT cb, int *err, void *pv);
int DIAMONDAPI CloseProc(int hf, int *err, void *pv);
long DIAMONDAPI SeekProc(int hf, long dist, int seektype, int *err, void *pv);
int DIAMONDAPI DeleteProc(char *pszFile, int *err, void *pv);
BOOL DIAMONDAPI TempFileProc(char *pszTempName, int cbTempName, void *pv);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	char  szOutputDirectoryPath[] = "c:\\";
	char  szCreateFileName[] = "sample.cab";
	int   nFileCount = 2;
	LPSTR lpszAddFilePath[] = {"c:\\sample.txt", "c:\\sample.bmp"};
	LPSTR lpszAddDirectryPath[] = {"data\\text\\", "data\\bitmap\\"};
	TCOMP tcomp = tcompTYPE_MSZIP;
	BOOL  bResult;

	bResult = StartFci(szOutputDirectoryPath, szCreateFileName, lpszAddFilePath, lpszAddDirectryPath, nFileCount, tcomp);
	if (bResult)
		MessageBox(NULL, TEXT("キャビネットファイルを作成しました。"), TEXT("OK"), MB_OK);
	
	return 0;
}

BOOL StartFci(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, LPSTR *lpszAddFilePath, LPSTR *lpszAddDirectryPath, int nFileCount, TCOMP tcomp)
{
	HFCI  hfci;
	ERF   erf;
	BOOL  bResult;
	TCHAR szBuf[256];
	
	hfci = CreateFciContext(lpszOutputDirectoryPath, lpszCreateFileName, &erf);
	if (hfci == NULL) {
		wsprintf(szBuf, TEXT("FCIコンテキストの作成に失敗しました。 ErrorCode %d"), erf.erfOper);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		return FALSE;
	}

	bResult = AddFile(hfci, lpszAddFilePath, lpszAddDirectryPath, nFileCount, tcomp);
	if (!bResult) {
		wsprintf(szBuf, TEXT("ファイルの追加に失敗しました。 ErrorCode %d"), erf.erfOper);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		FCIDestroy(hfci);
		return FALSE;
	}

	bResult = FCIFlushCabinet(hfci, FALSE, NULL, StatusProc);
	if (!bResult) {
		wsprintf(szBuf, TEXT("キャビネットファイルの作成に失敗しました。 ErrorCode %d"), erf.erfOper);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		FCIDestroy(hfci);
		return FALSE;
	}

	FCIDestroy(hfci);

	return TRUE;
}

HFCI CreateFciContext(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, PERF perf)
{
	HFCI hfci;
	CCAB cab;

	ZeroMemory(&cab, sizeof(CCAB));
	lstrcpyA(cab.szCab, lpszCreateFileName);
	lstrcpyA(cab.szCabPath, lpszOutputDirectoryPath);

	hfci = FCICreate(perf, FilePlaceProc, AllocProc, FreeProc, 
		OpenProc, ReadProc, WriteProc, CloseProc, SeekProc, DeleteProc,
		TempFileProc, &cab, NULL);
	
	return hfci;
}

BOOL AddFile(HFCI hfci, LPSTR *lpszFilePath, LPSTR *lpszDirectryPath, int nFileCount, TCOMP tcomp)
{
	int   i;
	char  szFileName[256];
	BOOL  bResult = FALSE;
	LPSTR lpszFileName;

	for (i = 0; i < nFileCount; i++) {
		lpszFileName = PathFindFileNameA(lpszFilePath[i]);
		if (lpszDirectryPath[i] != NULL) {
			lstrcpyA(szFileName, lpszDirectryPath[i]);
			lstrcatA(szFileName, lpszFileName);
		}
		else
			lstrcpyA(szFileName, lpszFileName);

		bResult = FCIAddFile(hfci, lpszFilePath[i], szFileName, FALSE, NULL, StatusProc, GetOpenInfoProc, tcomp);
		if (!bResult)
			break;
	}

	return bResult;
}

int DIAMONDAPI GetOpenInfoProc(char *pszName, USHORT *pdate, USHORT *ptime, USHORT *pattribs, int *err, void *pv)
{
	HANDLE   hFile;
	DWORD    dwDosFileAttributes;
	FILETIME fileTime;
	FILETIME localFileTime;

	hFile = CreateFileA(pszName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return -1;

	GetFileTime(hFile, NULL, NULL, &fileTime);
	FileTimeToLocalFileTime(&fileTime, &localFileTime);
	FileTimeToDosDateTime(&localFileTime, pdate, ptime);

	dwDosFileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE;
	*pattribs = (USHORT)(GetFileAttributesA(pszName) & dwDosFileAttributes);

	return (int)hFile;
}

long DIAMONDAPI StatusProc(UINT typeStatus, ULONG cb1, ULONG cb2, void *pv)
{
	return 0;
}

int DIAMONDAPI FilePlaceProc(PCCAB pccab, char *pszFile, long cbFile, BOOL fContinuation, void *pv)
{
	return 0;
}

void * DIAMONDAPI AllocProc(ULONG cb)
{
	return HeapAlloc(GetProcessHeap(), 0, cb);
}

void DIAMONDAPI FreeProc(void *memory)
{
	HeapFree(GetProcessHeap(), 0, memory);
}

int DIAMONDAPI OpenProc(char *pszFile, int oflag, int pmode, int *err, void *pv)
{
	HANDLE hFile;

	hFile = CreateFileA(pszFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND)
		hFile = CreateFileA(pszFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	return (int)hFile;
}

UINT DIAMONDAPI ReadProc(int hf, void *memory, UINT cb, int *err, void *pv)
{
	DWORD dwReadByte;

	ReadFile((HANDLE)hf, memory, cb, &dwReadByte, NULL);
	
	return dwReadByte;
}

UINT DIAMONDAPI WriteProc(int hf, void *memory, UINT cb, int *err, void *pv)
{
	DWORD dwWriteByte;

	WriteFile((HANDLE)hf, memory, cb, &dwWriteByte, NULL);

	return dwWriteByte;
}

int DIAMONDAPI CloseProc(int hf, int *err, void *pv)
{
	CloseHandle((HANDLE)hf);

	return 0;
}

long DIAMONDAPI SeekProc(int hf, long dist, int seektype, int *err, void *pv)
{
	return SetFilePointer((HANDLE)hf, dist, 0, seektype);
}

int DIAMONDAPI DeleteProc(char *pszFile, int *err, void *pv)
{
	DeleteFileA(pszFile);

	return 0;
}

BOOL DIAMONDAPI TempFileProc(char *pszTempName, int cbTempName, void *pv)
{
	char szTempPath[256];

	GetTempPathA(cbTempName, szTempPath);
	GetTempFileNameA(szTempPath, "xx", 0, pszTempName);

	return TRUE;
}

WinMainにlpszAddDirectryPathという配列が宣言されています。 この配列の各要素はlpszAddFilePathの各要素に対応しており、 その要素のディレクトリ名を定義しています。 つまり、1番目の要素であれば、sample.txtはdataフォルダの中にあるtextフォルダの下に作成されるということになります。 dataフォルダが最上位に位置しているため、展開処理をしたときに確認できるのは、 dataフォルダということになります。 lpszAddDirectryPathはStartFciへと渡り、AddFileで使用されています。

BOOL AddFile(HFCI hfci, LPSTR *lpszFilePath, LPSTR *lpszDirectryPath, int nFileCount, TCOMP tcomp)
{
	int   i;
	char  szFileName[256];
	BOOL  bResult = FALSE;
	LPSTR lpszFileName;

	for (i = 0; i < nFileCount; i++) {
		lpszFileName = PathFindFileNameA(lpszFilePath[i]);
		if (lpszDirectryPath[i] != NULL) {
			lstrcpyA(szFileName, lpszDirectryPath[i]);
			lstrcatA(szFileName, lpszFileName);
		}
		else
			lstrcpyA(szFileName, lpszFileName);

		bResult = FCIAddFile(hfci, lpszFilePath[i], szFileName, FALSE, NULL, StatusProc, GetOpenInfoProc, tcomp);
		if (!bResult)
			break;
	}

	return bResult;
}

lpszDirectryPathがNULLでないときは、ファイルをディレクトリの下に置きたいということで、 まずディレクトリのパスをszFileNameにコピーし、その後ろにファイル名を連結させています。

FCIAddFileと_A_EXEC属性

FCIAddFileの第4引数は、キャビネットファイルの展開時にそのファイルを実行するかを 表すものだと思われますが、実際にはTRUEを指定してもファイルが実行されることはありません。 _A_EXECという属性がfci.hに定義されていることから、MS-DOS形式のファイル属性として 有効なビットだと思われますが、FCIAddFileの呼び出しで追加されることはなく、 また、GetOpenInfoProcのpattribsに明示的に指定してもビットは有効になりません。 こうなってしまうと、キャビネットファイルを展開するアプリケーションからすれば、 どのファイルを実行すればよいかの情報を得る手掛かりがありません。 展開時に実行という概念は、普通にディスク上に存在するキャビネットファイルに適応されると戸惑いますが、 自己解凍スタブと組み合わせた場合は、そのスタブがキャビネットファイルに含まれる 実行ファイル(たとえば、インストーラー)を起動してくれるということで、 便利な概念であるといえるはずです。



戻る