EternalWindows
OLE埋め込み / IDataObjectの実装

コンテナがオブジェクトを埋め込んでいるように見せるためには、 コンテナがオブジェクトのイメージを描画できなければなりません。 この描画にはIViewObject2というインターフェースが使用されますが、 これを実装するのはデフォルトハンドラのオブジェクトであり、 埋め込み元のオブジェクトは通常実装しません。 理由は、描画の際に使用するデバイスコンテキストを、プロセスを超えてマーシャリングすることができないためです。 埋め込み元のオブジェクトがIDataObjectを通じてデータを返せば、 デフォルトのハンドラのオブジェクトはこれを取得することができるため、 埋め込み元のオブジェクトのイメージを描画できるようになります。 次に、IDataObjectのメンバを順に示します。

STDMETHODIMP CObject::GetData(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
{
	if (pformatetcIn->cfFormat == CF_METAFILEPICT) {
		pmedium->tymed = TYMED_MFPICT;
		pmedium->pUnkForRelease = NULL;
		pmedium->hGlobal = GetMetaFileData(FALSE);
		
	}
	else if (pformatetcIn->cfFormat == CF_ENHMETAFILE) {
		pmedium->tymed = TYMED_ENHMF;
		pmedium->pUnkForRelease = NULL;
		pmedium->hEnhMetaFile = (HENHMETAFILE)GetMetaFileData(TRUE);
	}
	else
		return E_FAIL;
	
	return S_OK;
}

GetDataは、オブジェクトのデータを第1引数のフォーマットで第2引数に返します。 今回のオブジェクトに対応するコンテナは、データをCF_METAFILEPICTで要求するため、 このときはSTGMEDIUM.tymedにTYMED_MFPICTを格納し、 STGMEDIUM.hGlobalにメタファイルのデータを格納します。 一方、WordやExcelではデータをCF_ENHMETAFILEで要求するため、 このときはSTGMEDIUM.tymedにTYMED_ENHMFを格納し、 STGMEDIUM.hGlobalに拡張メタファイルのデータを格納します。 GetMetaFileDataの内部については後述します。

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

GetDataHereは、GetDataと同じようにフォーマットしたデータを返すことになりますが、 データを受け取るためのメモリは呼び出し側が提供しています。 よって、そこに対してデータを格納するという点がGetDataと異なります。 基本的に呼ばれることはないようなので、E_NOTIMPLを返しています。

STDMETHODIMP CObject::QueryGetData(FORMATETC *pformatetc)
{
	if (pformatetc->cfFormat == CF_METAFILEPICT || pformatetc->cfFormat == CF_ENHMETAFILE)
		return S_OK;

	return S_FALSE;
}

QueryGetDataは、引数で指定されたフォーマットをオブジェクトがサポートしているかどうかを返します。 今回のオブジェクトは、CF_METAFILEPICTとCF_ENHMETAFILEをサポートしているため、 これらの場合はS_OKを返すようにします。

STDMETHODIMP CObject::GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut)
{
	return DATA_S_SAMEFORMATETC;
}

GetCanonicalFormatEtcは、データオブジェクトの一般的なフォーマットを取得する目的で呼ばれます。 今回の場合、オブジェクトはCF_METAFILEPICTとCF_ENHMETAFILEをサポートするわけですが、 どちらの場合でも返されるイメージに差はなく、実質的にフォーマットの違いはないといえます。 このような場合は、DATA_S_SAMEFORMATETCを返せば問題ありません。

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

SetDataは、コンテナがオブジェクトにデータを設定する目的で使用できますが、基本的に呼ばれることはありません。 今回は実装しないということでE_NOTIMPLを返しています。

STDMETHODIMP CObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc)
{
	return E_NOTIMPL;
}

EnumFormatEtcは、オブジェクトがサポートするフォーマットを列挙する目的で使用できますが、基本的に呼ばれることはありません。 今回は実装しないということでE_NOTIMPLを返しています。

STDMETHODIMP CObject::DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
	if (m_pDataAdviseHolder == NULL)
		CreateDataAdviseHolder(&m_pDataAdviseHolder);

	return m_pDataAdviseHolder->Advise(static_cast(this), pformatetc, advf, pAdvSink, pdwConnection);
}

STDMETHODIMP CObject::DUnadvise(DWORD dwConnection)
{
	if (m_pDataAdviseHolder != NULL)
		m_pDataAdviseHolder->Unadvise(dwConnection);

	return S_OK;
}

STDMETHODIMP CObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise)
{
	if (m_pDataAdviseHolder != NULL)
		m_pDataAdviseHolder->EnumAdvise(ppenumAdvise);

	return S_OK;
}

これらのメソッドの実装は、前節で取り上げたIOleObject::Adviseなどの要領と似ています。 DAdviseが呼ばれた場合は、データが変更された場合にIAdviseSink::OnDataChangeを呼び出す必要があるのですが、 そのために必要なpAdvSinkをメンバに保存してしまっては、 その単一のクライアントに対してしか通知を送れないことになります。 CreateDataAdviseHolderでアドバイスホルダを作成すれば、 複数のpAdvSinkを一括で管理することができ、 IDataAdviseHolder::SendOnDataChangeで一斉に通知できるようになります。

既に示したGetDataでは、メタファイル形式のデータを取得するためにGetMetaFileDataというメソッドを呼び出していました。 このメソッドの実装は次のようになっています。

HGLOBAL CObject::GetMetaFileData(BOOL bEnhance)
{
	HDC            hdc;
	HMETAFILE      hmf;
	HGLOBAL        hglobal;
	LPMETAFILEPICT lpmf;
	SIZEL          sizel;
	int            nWidth, nHeight;
	
	hdc = (HDC)CreateMetaFile(NULL);
	
	GetSize(&nWidth, &nHeight);

	SetMapMode(hdc, MM_ANISOTROPIC);
	SetWindowOrgEx(hdc, 0, 0, NULL);
	SetWindowExtEx(hdc, nWidth, nHeight, NULL);
	
	DrawShape(hdc);
	
	hmf = CloseMetaFile(hdc);

	sizel.cx = nWidth;
	sizel.cy = nHeight;
	DPtoHIMETRIC(&sizel);

	hglobal = GlobalAlloc(GPTR, sizeof(METAFILEPICT));
	lpmf = (LPMETAFILEPICT)GlobalLock(hglobal);
	lpmf->hMF = hmf;
	lpmf->mm = MM_ANISOTROPIC;
	lpmf->xExt = sizel.cx;
	lpmf->yExt = sizel.cy;
	GlobalUnlock(hglobal);

	if (bEnhance) {
		DWORD   dwSize;
		LPBYTE  lpData;
		HGLOBAL hglobalOld = hglobal;
		
		dwSize = GetMetaFileBitsEx(lpmf->hMF, 0, NULL);
		lpData = (LPBYTE)GlobalAlloc(GPTR, dwSize);
		GetMetaFileBitsEx(lpmf->hMF, dwSize, lpData);
		hglobal = (HGLOBAL)SetWinMetaFileBits(dwSize, lpData, NULL, lpmf);
		GlobalFree(lpData);
		GlobalFree(hglobalOld);
	}

	return hglobal;
}

CreateMetaFileを呼び出せば、メタファイルのデバイスコンテキストを取得することができます。 このデバイスコンテキストに対して描画を行えば、その描画した図形が記録されることになるため、 DrawShapeを呼び出してオブジェクト上に作成された全ての図形を描画します。 このとき事前にマッピングモードを設定しておくことが重要で、 SetMapModeにMM_ANISOTROPICを指定するようにします。 これで描画の範囲を論理単位で指定できるようになるため、 SetWindowOrgExで原点を設定し、SetWindowExtExでサイズを設定します。 CloseMetaFileを呼び出せばメタファイルのハンドルを取得することができますが、 データはMETAFILEPICT構造体でフォーマットされていなければならないため、 GlobalAllocでそのためのサイズを取得しています。 METAFILEPICT構造体の初期化については上記の通りになります。 xExtやyExtはHIMETRIC単位でなければならないことに注意してください。

bEnhanceがTRUEである場合は、メタファイルを拡張メタファイルに変換することになります。 このためには、現在のメタファイルのビット列が必要になるため、 これを格納するためのバッファを取得することになります。 具体的には、GetMetaFileBitsExの第3引数にNULLを指定して必要なサイズを取得し、 データを格納できるだけのバッファを確保してから、再びGetMetaFileBitsExを呼び出します。 拡張メタファイルへの変換は、SetWinMetaFileBitsで可能です。


戻る