EternalWindows
ZIP / IDropTargetによる圧縮

ZIPファイルの圧縮や展開といった機能が、zipfldr.dllに実装されていることはこれまで述べてきた通りです。 問題となるのは、このDLLをどのように使用すればよいかが分からないという点ですが、 圧縮が行われる一般的な場面というものを考えてみると、意外なヒントを得ることがでるものです。 たとえば、既存のZIPファイルに新たなファイルを追加するような場合は、 そのファイルをZIPファイルにドロップすることがありますが、 この場面ではzipfldr.dllの圧縮機能が使用されていると推測することができます。 よって、このドロップがどのような仕組みで実現されているかを考えていけば、 圧縮の手掛かりを得ることができるかもしれません。 結論からいうと、この仕組みは前節でも述べたシェル拡張によって実現されています。

DropHandlerというのが、特定のファイルに対してのドロップを検出するシェル拡張です。 このシェル拡張はIDropTargetを実装しており、これを通じてドロップをシミュレートすれば結果的にファイルの圧縮が行われるように思えますが、 実際にはそうとは限りません。 現に上記のCLSIDをCoCreateInstanceに指定して試したところ、 IDropTarget::Enterの時点でエラーが発生していました。 よって、当初はIDropTargetによる圧縮は不可能なのかと考えていたのですが、 zipfldr.dllをキーワードとしてレジストリで検索を行ったところ、 次のような別のCLSIDを発見することに成功しました。

上記したDropHandlerのCLSIDは先のDropHandlerのCLSIDとは異なっており、 実際にCoCreateInstanceに指定して試したところ、ファイルの圧縮に成功することを確認しました。 InProcServer32キーから分かるように、CLSIDで識別されるオブジェクトはzipfldr.dllに実装されています。

今回のプログラムは、IDropTargetを使用してファイルを圧縮します。

#include <windows.h>
#include <shlobj.h>

class CDataObject : public IDataObject, public IEnumFORMATETC
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium);
	STDMETHODIMP GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium);
	STDMETHODIMP QueryGetData(FORMATETC *pformatetc);
	STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut);
	STDMETHODIMP SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease);
	STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc);
	STDMETHODIMP DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection);
	STDMETHODIMP DUnadvise(DWORD dwConnection);
	STDMETHODIMP EnumDAdvise(IEnumSTATDATA **ppenumAdvise);
	
	STDMETHODIMP Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched);
	STDMETHODIMP Skip(ULONG celt);
	STDMETHODIMP Reset(VOID);
	STDMETHODIMP Clone(IEnumFORMATETC **ppenum);
	
	CDataObject(LPSTR lpszFileName[], ULONG uFileCount);
	~CDataObject();

private:
	LONG  m_cRef;
	ULONG m_uEnumCount;
	ULONG m_uFileCount;
	LPSTR *m_lpszFileName;
};

BOOL Zip(IDropTarget *pDropTarget, LPSTR lpszFileName[], UINT uFileCount);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HRESULT     hr;
	IDropTarget *pDropTarget;
	CLSID       CLSID_DropHandler = {0x888dca60, 0xfc0a, 0x11cf, {0x8f, 0x0f, 0x00, 0xc0, 0x4f, 0xd7, 0xd0, 0x62}};
	LPSTR       lpszFileName[] = {
		"C:\\file1.txt",
		"C:\\file2.txt"
	};

	CoInitialize(NULL);

	hr = CoCreateInstance(CLSID_DropHandler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDropTarget));
	if (FAILED(hr)) {
		CoUninitialize();
		return 0;
	}

	if (Zip(pDropTarget, lpszFileName, 2))
		MessageBox(NULL, TEXT("ZIPファイルを作成しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("ZIPファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

	pDropTarget->Release();
	CoUninitialize();

	return 0;
}

BOOL Zip(IDropTarget *pDropTarget, LPSTR lpszFileName[], UINT uFileCount)
{
	HRESULT     hr;
	POINTL      pt;
	DWORD       dwEffect;
	IDataObject *pDataObject;

	pDataObject = new CDataObject(lpszFileName, uFileCount);

	pt.x = 0; 
	pt.y = 0;
	dwEffect = DROPEFFECT_COPY;
	hr = pDropTarget->DragEnter(pDataObject, 0, pt, &dwEffect);
	if (FAILED(hr)) {
		pDataObject->Release();
		return FALSE;
	}
	
	dwEffect = DROPEFFECT_COPY;
	hr = pDropTarget->Drop(pDataObject, 0, pt, &dwEffect);
	if (FAILED(hr)) {
		pDataObject->Release();
		return FALSE;
	}

	pDropTarget->DragLeave();
	pDataObject->Release();

	return TRUE;
}


// CDataObject


CDataObject::CDataObject(LPSTR lpszFileName[], ULONG uFileCount)
{
	m_cRef = 1;
	m_uEnumCount = 0;
	m_uFileCount = uFileCount;
	m_lpszFileName = lpszFileName;
}

CDataObject::~CDataObject()
{
}

STDMETHODIMP CDataObject::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDataObject))
		*ppvObject = static_cast<IDataObject *>(this);
	else if (IsEqualIID(riid, IID_IEnumFORMATETC))
		*ppvObject = static_cast<IEnumFORMATETC *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CDataObject::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CDataObject::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
{
	ULONG       i;
	ULONG       uSize = 0;
	LPSTR       lp;
	HDROP       hDrop;
	LPDROPFILES lpDropFiles;
	
	if (pformatetcIn->cfFormat != CF_HDROP)
		return E_FAIL;

	for (i = 0; i < m_uFileCount; i++)
		uSize += lstrlenA(m_lpszFileName[i]) + 1;
	
	hDrop = (HDROP)GlobalAlloc(GHND, sizeof(DROPFILES) + uSize);
	lpDropFiles = (LPDROPFILES)GlobalLock(hDrop);
	lpDropFiles->pFiles = sizeof(DROPFILES);
	lpDropFiles->pt.x = 0;
	lpDropFiles->pt.y = 0;
	lpDropFiles->fNC = FALSE;
	lpDropFiles->fWide = FALSE;

	lp = (LPSTR)&lpDropFiles[1];

	for (i = 0; i < m_uFileCount; i++) {
		lstrcpyA(lp, m_lpszFileName[i]);
		lp += lstrlenA(m_lpszFileName[i]) + 1;
	}

	GlobalUnlock(hDrop);

	pmedium->tymed = TYMED_HGLOBAL;
	pmedium->hGlobal = (HGLOBAL)hDrop;
	pmedium->pUnkForRelease = NULL;

	return S_OK;
}

STDMETHODIMP CDataObject::GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::QueryGetData(FORMATETC *pformatetc)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc)
{
	if (dwDirection == DATADIR_GET)
		return QueryInterface(IID_PPV_ARGS(ppenumFormatEtc));
	else
		return E_NOTIMPL;
}

STDMETHODIMP CDataObject::DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::DUnadvise(DWORD dwConnection)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched)
{
	if (m_uEnumCount >= 1)
		return E_FAIL;
	
	rgelt->cfFormat = CF_HDROP;
	rgelt->ptd      = NULL;
	rgelt->dwAspect = 0;
	rgelt->lindex   = -1;
	rgelt->tymed    = TYMED_HGLOBAL;

	if (pceltFetched != NULL)
		*pceltFetched = 1;
	
	m_uEnumCount++;

	return S_OK;
}

STDMETHODIMP CDataObject::Skip(ULONG celt)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::Reset(VOID)
{
	return E_NOTIMPL;
}

STDMETHODIMP CDataObject::Clone(IEnumFORMATETC **ppenum)
{
	return E_NOTIMPL;
}

CDataObjectというクラスを定義しているのは、IDropTargetのメソッドであるDragEnterとDropが、 IDataObjectを実装したオブジェクトを要求するからです。 CDataObjectはIEnumFORMATETCも実装していますが、 これはIDataObject::EnumFormatEtcがIEnumFORMATETCを必要としているからです。 DragEnterとDropは自作関数のZip内で呼ばれ、 この関数はlpszFileNameで識別されるファイルをZIPファイルとして圧縮します。 作成されるZIPファイルの名前は配列の先頭要素を反映するため、 今回の場合であればfile1.zipになります。

Zipの実装は次のようになっています。

BOOL Zip(IDropTarget *pDropTarget, LPSTR lpszFileName[], UINT uFileCount)
{
	HRESULT     hr;
	POINTL      pt;
	DWORD       dwEffect;
	IDataObject *pDataObject;

	pDataObject = new CDataObject(lpszFileName, uFileCount);

	pt.x = 0; 
	pt.y = 0;
	dwEffect = DROPEFFECT_COPY;
	hr = pDropTarget->DragEnter(pDataObject, 0, pt, &dwEffect);
	if (FAILED(hr)) {
		pDataObject->Release();
		return FALSE;
	}
	
	dwEffect = DROPEFFECT_COPY;
	hr = pDropTarget->Drop(pDataObject, 0, pt, &dwEffect);
	if (FAILED(hr)) {
		pDataObject->Release();
		return FALSE;
	}

	pDropTarget->DragLeave();
	pDataObject->Release();

	return TRUE;
}

圧縮を行うために呼び出さなければならないのは、DragEnterとDropです。 本来、DragEnterはZIPファイル上にファイルを接触させた場合に呼び出し、 Dropはファイルをドロップした場合に呼び出しますが、 今回はドラッグをシミュレートするだけですから連続して呼び出しても問題ありません。 DragEnterとDropの引数の意味は同じであり、 第1引数にはIDataObjectを実装したオブジェクトのアドレスを指定することになります。 このオブジェクトは、new CDataObjectによって作成されています。 第2引数は0で問題なく、第3引数のドラッグ位置に関しても0で初期化しておいて問題ありません。 第4引数に指定する変数にはDROPEFFECT_COPYを指定しておきます。

IDropTarget::DragEnterを呼び出した場合は、内部でIDataObject::EnumFormatEtcが呼ばれることになります。

STDMETHODIMP CDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc)
{
	if (dwDirection == DATADIR_GET)
		return QueryInterface(IID_PPV_ARGS(ppenumFormatEtc));
	else
		return E_NOTIMPL;
}

dwDirectionがDATADIR_GETである場合は、IEnumFORMATETCを返す必要があります。 CDataObjectはこのインターフェースを実装しているため、 QueryInterfaceを呼び出すことでIEnumFORMATETCを取得することができます。 IEnumFORMATETCを取得したDropHandlerはIEnumFORMATETC::Nextを呼び出します。

STDMETHODIMP CDataObject::Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched)
{
	if (m_uEnumCount >= 1)
		return E_FAIL;
	
	rgelt->cfFormat = CF_HDROP;
	rgelt->ptd      = NULL;
	rgelt->dwAspect = 0;
	rgelt->lindex   = -1;
	rgelt->tymed    = TYMED_HGLOBAL;

	if (pceltFetched != NULL)
		*pceltFetched = 1;
	
	m_uEnumCount++;

	return S_OK;
}

このメソッドでは、CDataObjectが維持しているデータのフォーマットを返すことになります。 CDataObjectはコンストラクタで圧縮されるべきファイルのフルパスを受け取っているため、 データのフォーマットは文字列(CF_TEXT)になるように思えますが、そうではありません。 DropHandlerはデータのフォーマットがCF_HDROPであることを前提としているため、 cfFormatはそのように初期化しておくことになります。 サポートするフォーマットが1つだけであるため、2回目以降のNextの呼び出しではE_FAILを返すようにします。 このために、列挙された回数をm_uEnumCountに保存しています。

IDropTarget::Dropを呼び出した場合は、内部でIDataObject::GetDataが呼ばれることになります。

STDMETHODIMP CDataObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
{
	ULONG       i;
	ULONG       uSize = 0;
	LPSTR       lp;
	HDROP       hDrop;
	LPDROPFILES lpDropFiles;
	
	if (pformatetcIn->cfFormat != CF_HDROP)
		return E_FAIL;

	for (i = 0; i < m_uFileCount; i++)
		uSize += lstrlenA(m_lpszFileName[i]) + 1;
	
	hDrop = (HDROP)GlobalAlloc(GHND, sizeof(DROPFILES) + uSize);
	lpDropFiles = (LPDROPFILES)GlobalLock(hDrop);
	lpDropFiles->pFiles = sizeof(DROPFILES);
	lpDropFiles->pt.x = 0;
	lpDropFiles->pt.y = 0;
	lpDropFiles->fNC = FALSE;
	lpDropFiles->fWide = FALSE;

	lp = (LPSTR)&lpDropFiles[1];

	for (i = 0; i < m_uFileCount; i++) {
		lstrcpyA(lp, m_lpszFileName[i]);
		lp += lstrlenA(m_lpszFileName[i]) + 1;
	}

	GlobalUnlock(hDrop);

	pmedium->tymed = TYMED_HGLOBAL;
	pmedium->hGlobal = (HGLOBAL)hDrop;
	pmedium->pUnkForRelease = NULL;

	return S_OK;
}

このメソッドでは、CDataObjectが維持しているデータを第1引数の形式でフォーマットし、 そのフォーマットしたデータを第2引数に返します。 サポートするフォーマットはIEnumFORMATETC::Nextで示したようにCF_HDROPだけですから、 それ以外のフォーマットが指定された場合は処理を続行しないようにします。 フォーマットの具体的な手順としては、 まず圧縮したいファイルパスの合計サイズを求め、 これとDROPFILES構造体のサイズを加算してメモリを確保します。 そして、このメモリの先頭にDROPFILES構造体を格納するようにし、 その後に一連のファイルパスを格納するようにします。 DROPFILES構造体の初期化についてはfWideが特に重要なメンバで、 ファイルパスがUNICODE文字列である場合はTRUEを指定することになります。 &lpDropFiles[1]によってlpはDROPFILES構造体の後を指すようになり、 ファイルパスをコピーすべき場所を特定できたことになります。 メモリに一連の情報を格納できたならば、それをSTGMEDIUM構造体に格納することになります。 今回のようにGlobalAllocで確保したメモリはhGlobalメンバに指定し、 このメンバを初期化する場合はtymedにTYMED_HGLOBALを指定します。


戻る