EternalWindows
OLE描画 / データキャッシュ

アプリケーションが何からのデータを使用する場合、それが使えなくなった時のために予めデータを保存することがあります。 たとえば、クリップボードに存在するデータを使用したいアプリケーションは、 クリップボード内のデータが変更されても問題ないように、データを変数として保存することがあります。 OLEではこうしたデータの保存(キャッシュ)を簡単に行う手段として、 データキャッシュと呼ばれるオブジェクトを公開しています。 このオブジェクトにデータをキャッシュすれば、 データキャッシュの様々なサービスを使用できるようになります。 データキャッシュを作成するには、CreateDataCacheを呼び出します。

HRESULT CreateDataCache(
  LPUNKNOWN pUnkOuter,
  REFCLSID rclsid,
  REFIID iid,
  LPVOID *ppv
);

pUnkOuterは、集約に使用するインターフェースを指定します。 集約を行わない場合は、NULLで問題ありません。 rclsidは、基本的にCLSID_NULLを指定します。 riidは、データキャッシュを識別するインターフェースのIIDを指定します。 ppvは、データキャッシュのオブジェクトを受け取る変数のアドレスを指定します。

データキャッシュが実装しているインターフェースは、 IDataObject、IPersistStorage、IViewObject2、IOleCache2、IOleCacheControlなど様々存在しますが、 主に使用するのはIOleCacheです。 データキャッシュにデータをキャッシュするためには、キャッシュしたいデータのフォーマットを伝えておかなければならないため、 IOleCache::Cacheでこれを行います。

HRESULT IOleCache::Cache(
  FORMATETC *pformatetc,
  DWORD advf,
  DWORD *pdwConnection
);

pformatetcは、キャッシュしたいデータのフォーマットを指定します。 advfは、キャッシュの制御に関するADVF列挙型の定数を指定しますが、0でも問題ありません。 pdwConnectionは、特定のフォーマットのキャッシュをを識別する値を受け取る変数のアドレスを指定します。 キャッシュが不要になった場合は、この値をIOleCache::Uncacheに指定します。

フォーマットの設定を終えたら、実際にデータをキャッシュするためにIOleCache::SetDataを呼び出します。

HRESULT IOleCache::SetData(
  FORMATETC *pformatetc,
  STGMEDIUM *pmedium,
  BOOL fRelease
);

pformatetcは、キャッシュしたいデータのフォーマットを指定します。 pmediumは、キャッシュしたいデータを格納したSTGMEDIUM構造体のアドレスを指定します。 fReleaseは、キャッシュが不要になった際に、自動でデータを開放するかどうかを指定します。 TRUEを指定した場合は、自動でデータが開放されるようになります。

今回のプログラムは、クリップボードに存在するメタファイル形式のデータをキャッシュし、 そのデータをデータキャッシュのサービスを使用して描画します。 事前に、ペイントで表示される画像をクリップボードにコピーしておいてください。

#include <windows.h>

void HIMETRICtoDP(LPSIZEL lpSizel);
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 IOleCache *pOleCache = NULL;
	static DWORD     dwConnection = 0;

	switch (uMsg) {

	case WM_CREATE: {
		HRESULT     hr;
		FORMATETC   formatetc;
		STGMEDIUM   medium;
		IDataObject *pDataObject;

		OleInitialize(NULL);
		
		hr = OleGetClipboard(&pDataObject);
		if (FAILED(hr)) {
			MessageBox(NULL, TEXT("クリップボードにデータが存在しません。"), NULL, MB_ICONWARNING);
			return -1;
		}

		formatetc.cfFormat = CF_METAFILEPICT;
		formatetc.ptd      = NULL;
		formatetc.dwAspect = DVASPECT_CONTENT;
		formatetc.lindex   = -1;
		formatetc.tymed    = TYMED_MFPICT;

		hr = pDataObject->GetData(&formatetc, &medium);
		if (FAILED(hr)) {
			MessageBox(NULL, TEXT("メタファイル形式のデータの取得に失敗しました。"), NULL, MB_ICONWARNING);
			pDataObject->Release();
			return -1;
		}

		CreateDataCache(NULL, CLSID_NULL, IID_PPV_ARGS(&pOleCache));

		pOleCache->Cache(&formatetc, 0, &dwConnection);
		pOleCache->SetData(&formatetc, &medium, TRUE);

		pDataObject->Release();

		return 0;
	}

	case WM_PAINT: {
		HDC          hdc;
		RECT         rc;
		PAINTSTRUCT  ps;
		SIZEL        sizel;
		IViewObject2 *pViewObject2;

		hdc = BeginPaint(hwnd, &ps);

		pOleCache->QueryInterface(IID_PPV_ARGS(&pViewObject2));
		pViewObject2->GetExtent(DVASPECT_CONTENT, -1, NULL, &sizel);
		HIMETRICtoDP(&sizel);
		pViewObject2->Release();

		SetRect(&rc, 0, 0, sizel.cx, sizel.cy);
		OleDraw(pOleCache, DVASPECT_CONTENT, hdc, &rc);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (pOleCache != NULL) {
			if (dwConnection != 0)
				pOleCache->Uncache(dwConnection);
			pOleCache->Release();
		}
		OleUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

void HIMETRICtoDP(LPSIZEL lpSizel)
{
	HDC hdc;
	const int HIMETRIC_INCH = 2540;

	hdc = GetDC(NULL);
	lpSizel->cx = lpSizel->cx * GetDeviceCaps(hdc, LOGPIXELSX) / HIMETRIC_INCH;
	lpSizel->cy = lpSizel->cy * GetDeviceCaps(hdc, LOGPIXELSY) / HIMETRIC_INCH;
	ReleaseDC(NULL, hdc);
}

OleGetClipboardを呼び出せば、クリップボード内のデータをIDataObjectで識別することができます。 IDataObject::GetDataを呼び出しているのは、データキャッシュに設定するためのデータを取得するためであり、 cfFormatにCF_METAFILEPICTを指定していることから、メタファイル形式のデータを要求しています。 これが成功したら、CreateDataCacheでデータキャッシュを作成し、 IOleCache::CacheとIOleCache::SetDataでフォーマットとデータを設定します。 これで、データのキャッシュは完了したことになります。

今回、どういった目的でデータキャッシュを使用しているかというと、データをIViewObjectで描画するためです。 OleGetClipboardで取得したオブジェクトはIViewObjectを実装していませんが、 データキャッシュにデータをキャッシュすれば、データキャッシュのIViewObjectを使えるという寸法です。 描画は前節と同様にOleDrawで行っていますが、描画先の範囲をデータのサイズを一致させています。 データのサイズはIViewObject2::GetExtentで取得可能であり、 これはHIMETRIC単位になっているため、HIMETRICtoDPという自作関数で変換しています。


戻る