EternalWindows
Cabinet API / 圧縮ステータス

FCI/FDIは、コールバック関数の機能が非常に充実しています。 これは一重にアプリケーションにより多くの状態遷移を通知したいということなので、 圧縮状況などを知りたいような場合は、積極的に利用するべきです。 FCIAddFile及びFCIFlushCabinetは、内部で処理を行っているときに、 PFNFCISTATUS型のコールバック関数を数回に渡って呼び出すことになるため、 この関数の使い方を検討します。

long DIAMONDAPI StatusProc(UINT typeStatus, ULONG cb1, ULONG cb2, void *pv)
{
	TCHAR szBuf[256];
	ULONG uPercentage;

	if (typeStatus == statusFile) {
		wsprintf(szBuf, TEXT("圧縮量 %d"), cb1);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_ICONOK);
	}
	else if (typeStatus == statusFolder) {
		while (cb1 > 10000000) {
			cb1 >>= 3;
			cb2 >>= 3;
		}
		if (cb2 == 0)
			uPercentage = 0;
		else
			uPercentage = (cb1 * 100) / cb2;
		wsprintf(szBuf, TEXT("コピー率 : %d %"), uPercentage);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_ICONOK);
	}
	else if (typeStatus == statusCabinet) {
		MessageBox(NULL, TEXT("完了"), TEXT("OK"), MB_ICONOK);
	}
	else
		;

	return 0;
}

typeStatusがstatusFileである場合、FCIAddFileの内部で圧縮したデータを フォルダ(一時的なバッファ)に書き込んでいる途中であることを意味しています。 その現在の圧縮量はcb1に格納され、その圧縮量を非圧縮量に換算したら どれくらいの値かはcb2に格納されています。 typeStatusがstatusFolderである場合は、バッファをキャビネットにコピーしている 途中であることを示しています。 この関数を呼び出すのは、FCIAddFileまたはFCIFlushCabinetです。 FCIAddFileの場合は、typeStatusをstatusFileとした一連の呼び出しを終えてからとなります。 cb1とcb2を上記のように利用すれば、どれくらいコピーできたのかを知ることができるようです。 typeStatusがstatusCabinetである場合は、キャビネットをディスク上にファイルとして 完全に作成し終えたことを意味します。

typeStatusがstatusFolderであるときはコピー処理が行われていることを意味し、 それが終了した場合は、キャビネットに圧縮されたデータを持ったファイルが 作成されたことを意味します。 この通知は、FCICreateで指定したPFNFCIFILEPLACED型のコールバック関数で 受け取ることができます。 次に、コード例を示します。

int DIAMONDAPI FilePlaceProc(PCCAB pccab, char *pszFile, long cbFile, BOOL fContinuation, void *pv)
{
	char szBuf[256];

	wsprintfA(szBuf, "%s がキャビネットの中に作成されました。", pszFile);
	MessageBoxA(NULL, szBuf, "OK", MB_ICONOK);

	return 0;
}

コピーされた圧縮データは、pszFileの名前を持ったファイルとなります。 この名前はキャビネット上におけるファイル名ですから、 FCIAddFileの第3引数と同一になっているはずです。 cbFileには、ファイルのサイズが格納されています。

今回のプログラムはGUIとして設計し、作成したリストボックスに コールバック関数より取得した圧縮ステータスを追加していきます。 これまで曖昧であった一時ファイルの役割を少しだけ垣間見ることができると思われます。

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

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

#define ID_COMPRESS 100

struct DATA {
	HWND  hwndListBox;
	ULONG uTotalCompressSize;
	ULONG uTotalUncompressSize;
	char  szFileName[256];
};
typedef struct DATA DATA;
typedef DATA *LPDATA;

BOOL StartFci(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, LPSTR *lpszAddFilePath, LPSTR *lpszAddDirectryPath, int nFileCount, TCOMP tcomp, void *pv);
HFCI CreateFciContext(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, PERF perf, void *pv);
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);

HMENU InitializeMenu(void);
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  hwndListBox = NULL;
	static HMENU hmenu = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		hmenu = InitializeMenu();
		SetMenu(hwnd, hmenu);
		return 0;
	
	case WM_COMMAND:
		if (LOWORD(wParam) == ID_COMPRESS) {
			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;
			DATA  data;

			SendMessage(hwndListBox, LB_RESETCONTENT, 0, 0);

			EnableMenuItem(hmenu, ID_COMPRESS, MF_GRAYED);

			data.hwndListBox          = hwndListBox;
			data.uTotalCompressSize   = 0;
			data.uTotalUncompressSize = 0;

			StartFci(szOutputDirectoryPath, szCreateFileName, lpszAddFilePath, lpszAddDirectryPath, nFileCount, tcomp, (void *)&data);
			
			EnableMenuItem(hmenu, ID_COMPRESS, MF_ENABLED);
			DrawMenuBar(hwnd);
		}
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

HMENU InitializeMenu(void)
{
	HMENU        hmenu;
	MENUITEMINFO mii;
	
	hmenu = CreateMenu();

	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_TYPE | MIIM_ID;
	mii.fType      = MFT_STRING;
	mii.wID        = ID_COMPRESS;
	mii.dwTypeData = TEXT("圧縮開始");

	InsertMenuItem(hmenu, 0, FALSE, &mii);

	return hmenu;
}

BOOL StartFci(LPSTR lpszOutputDirectoryPath, LPSTR lpszCreateFileName, LPSTR *lpszAddFilePath, LPSTR *lpszAddDirectryPath, int nFileCount, TCOMP tcomp, void *pv)
{
	HFCI  hfci;
	ERF   erf;
	BOOL  bResult;
	TCHAR szBuf[256];
	
	hfci = CreateFciContext(lpszOutputDirectoryPath, lpszCreateFileName, &erf, pv);
	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, void *pv)
{
	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, pv);
	
	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;

	lstrcpyA(((LPDATA)pv)->szFileName, pszName);

	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)
{
	LPDATA lp = (LPDATA)pv;
	char   szBuf[256];
	ULONG  uPercentage;

	if (typeStatus == statusFile) {
		lp->uTotalCompressSize += cb1;
		lp->uTotalUncompressSize += cb2;
		wsprintfA(szBuf, "%s の圧縮サイズ : %d byte <- %d byte", lp->szFileName, lp->uTotalCompressSize, lp->uTotalUncompressSize);
		SendMessageA(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		UpdateWindow(lp->hwndListBox);
	}
	else if (typeStatus == statusFolder) {
		while (cb1 > 10000000) {
			cb1 >>= 3;
			cb2 >>= 3;
		}
		if (cb2 == 0)
			uPercentage = 0;
		else
			uPercentage = (cb1 * 100) / cb2;
		wsprintfA(szBuf, "%s のコピー率 : %d %", lp->szFileName, uPercentage);
		SendMessageA(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		UpdateWindow(lp->hwndListBox);
	}
	else if (typeStatus == statusCabinet) {
		SendMessage(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("完了"));
		UpdateWindow(lp->hwndListBox);
	}
	else
		;

	return 0;
}

int DIAMONDAPI FilePlaceProc(PCCAB pccab, char *pszFile, long cbFile, BOOL fContinuation, void *pv)
{
	LPDATA lp = (LPDATA)pv;
	char   szBuf[256];

	wsprintfA(szBuf, "%s がキャビネットの中に作成されました。", pszFile);
	SendMessageA(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	
	lp->uTotalCompressSize = 0;
	lp->uTotalUncompressSize = 0;

	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;

	lstrcpyA(((LPDATA)pv)->szFileName, pszFile);

	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;
}

メニューに表示されている「圧縮開始」の項目が選択されるとWM_COMMNADが送られ、 ここでStartFciの呼び出しを行っています。 StartFciの内部処理についてはほぼ前節のコードと変わりありませんが、 今回はFCICreateの最後の引数に独自の構造体を指定するため、 そのための引数は追加されています。 CreateFciContextのFCICreateの呼び出しを確認します。

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

pvの実体はDATA構造体のアドレスであり、これを指定することで、 各コールバック関数からDATA構造体を参照できるようになります。 次に、StatusProcの内部を示します。

long DIAMONDAPI StatusProc(UINT typeStatus, ULONG cb1, ULONG cb2, void *pv)
{
	LPDATA lp = (LPDATA)pv;
	char   szBuf[256];
	ULONG  uPercentage;

	if (typeStatus == statusFile) {
		lp->uTotalCompressSize += cb1;
		lp->uTotalUncompressSize += cb2;
		wsprintfA(szBuf, "%s の圧縮サイズ : %d byte <- %d byte", lp->szFileName, lp->uTotalCompressSize, lp->uTotalUncompressSize);
		SendMessageA(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		UpdateWindow(lp->hwndListBox);
	}
	else if (typeStatus == statusFolder) {
		while (cb1 > 10000000) {
			cb1 >>= 3;
			cb2 >>= 3;
		}
		if (cb2 == 0)
			uPercentage = 0;
		else
			uPercentage = (cb1 * 100) / cb2;
		wsprintfA(szBuf, "%s のコピー率 : %d %", lp->szFileName, uPercentage);
		SendMessageA(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		UpdateWindow(lp->hwndListBox);
	}
	else if (typeStatus == statusCabinet) {
		SendMessage(lp->hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("完了"));
		UpdateWindow(lp->hwndListBox);
	}
	else
		;

	return 0;
}

DATA構造体にuTotalCompressSizeというメンバを用意したのは、 現在トータルとしてどれくらい圧縮したのかを表示したいと思ったからです。 cb1の値は今回施した圧縮量ですから、これを加算していけばよいことになります。 SendMessageの呼び出しの後にUpdateWindowを呼び出しているのは、 追加している文字列を直ちにリストボックスに表示させるためです。

StatusProcでは現在の処理の状況を確認できますが、 それがどのファイルに対して行われているかは引数から特定できません。 そこで、今回のプログラムでは、ファイルをオープンするコールバック関数でファイル名を保存し、 それをStatusProcで参照するようにしています。 1つ目のオープン関数は前節で説明したGetOpenInfoProcで、FCIAddFileの内部で呼ばれることになります。 ここで得られるファイル名は圧縮元となるファイル名であり、 FCIAddFileの第2引数と同じ値になります。 もう1つは、次の関数です。

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

	lstrcpyA(((LPDATA)pv)->szFileName, pszFile);

	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;
}

OpenProcはPFNFCIOPEN型で、FCICreateに指定していたものです。 この関数でオープンすることになるファイルは、一時ファイルもしくは ディスク上に作成されるキャビネットファイルとなっています。

リストボックスに表示されている文字列を基に、圧縮処理からキャビネットファイルの 作成までがどのような流れになっているかを確認してみましょう。 まず、typeStatusがstatusFileであるStatusProcが数回呼ばれ、 その後にtypeStatusがstatusFolderであるStatusProcが数回呼ばれています。 statusFileの処理は、圧縮データをバッファに書き込むということなので、 そのときに表示されているファイル名は圧縮元のファイル名となっています。 これは、問題ないと思われます。 statusFolderの処理は、バッファをキャビネットにコピーしているということで、 ここでのファイル名は一時ファイルの名前となっています。 つまり、バッファの正体というのは一時ファイルのことであると思われます。 そして、コピーを終えると、キャビネットにファイルが作成されるということで、 FilePlaceProcが呼ばれています。

バッファへの溜め込み

今回のプログラムの流れは以前に述べた、 バッファにデータを書き込んで直ぐにコピーするという方法ですが、 一連のデータをバッファにまとめて書き込んでからコピーするという方法もあり、 実際にはこちらの方が高い圧縮率を維持できる傾向にあります。 バッファがコピーされるかどうかの判定は圧縮データのサイズがバッファのサイズを上回っているかどうかであり、 このバッファのサイズというのは、CCAB構造体のcab.cbFolderThreshで指定することになっています。

cab.cbFolderThresh = 500 * 1024;

このようにした場合、バッファに書き込んだ圧縮データの合計サイズが500Kbを超えるとき、 FCIAddFileの内部でバッファをキャビネットにコピーする処理が行われます。 超えなかった場合は、FCIFlushCabinetでバッファがコピーされることになります。 なお、FCIFlushFoloderという関数を呼び出せば、 明示的にバッファをコピーするようなこともできます。



戻る