EternalWindows
ActiveX コントロール / コントロールの作成

ActiveXのコンテナはコントロールを表示することが目的ですから、 当然ながらコントロールを作成するための処理も行うことになります。 ActiveXコントロールといってもそれはCOMオブジェクトの一種ですから、 CoCreateInstanceで作成できます。

CoCreateInstance(CLSID_ShockwaveFlash, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pOleObject));

CoCreateInstanceの第1引数には、作成したいオブジェクトのCLISDを指定します。 今回はFlashを再生したいと考えているため、FlashのCLSIDを指定するようにします。 ActiveXコントロールの実体はDLLであるため、第3引数にはCLSCTX_INPROC_SERVERを指定します。 第4引数のm_pOleObjectの型はIOleObjectであり、ActiveXコントロールはこれを必ず実装しているはずです。 よって、このインターフェースでコントロールを識別することに問題はありません。

コントロールを作成したら、次はコントロールの初期化を行います。 この初期化の順番は、コントロールの情報ステータスの値によって変化します。

m_pOleObject->GetMiscStatus(DVASPECT_CONTENT, &m_dwMiscStatus);

if (m_dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST)
	m_pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));

InitNew();

if (!(m_dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST))
	m_pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));
}

まず、IOleObject::GetMiscStatusを呼び出して情報スタータスを取得します。 そして次に、IOleObject::SetClientSiteと永続記憶インターフェースを呼び出すことになるのですが、 この呼び出す順番が情報ステータスの値によって異なります。 OLEMISC_SETCLIENTSITEFIRSTが含まれている場合はSetClientSiteを先に呼び出すようにしますが、 OLEMISC_SETCLIENTSITEFIRSTが含まれていない場合は永続記憶インターフェース(IPersistXXX)を先に呼び出す必要があります。 SetClientSiteを呼び出す理由はコントロールにコンテナのインターフェースを渡すためであり、 これによりコントロールは必要に応じてコンテナと通信できます。 永続記憶インターフェースの呼び出しは、InitNewという自作メソッドで行われています。

BOOL CActiveXContainer::InitNew()
{
	BOOL                bResult = TRUE;
	IPersistStreamInit  *pPersistStreamInit;
	IPersistStorage     *pPersistStorage;
	IPersistMemory      *pPersistMemory;
	IPersistPropertyBag *pPersistPropertyBag;

	if (m_pOleObject->QueryInterface(IID_PPV_ARGS(&pPersistStreamInit)) == S_OK) {
		pPersistStreamInit->InitNew();
		pPersistStreamInit->Release();
	}
	else if (m_pOleObject->QueryInterface(IID_PPV_ARGS(&pPersistStorage)) == S_OK) {
		pPersistStorage->InitNew(NULL);
		pPersistStorage->Release();
	}
	else if (m_pOleObject->QueryInterface(IID_PPV_ARGS(&pPersistMemory)) == S_OK) {
		pPersistMemory->InitNew();
		pPersistMemory->Release();
	}
	else if (m_pOleObject->QueryInterface(IID_PPV_ARGS(&pPersistPropertyBag)) == S_OK) {
		pPersistPropertyBag->InitNew();
		pPersistPropertyBag->Release();
	}
	else
		bResult = FALSE;

	return bResult;
}

コントロールは、永続記憶インターフェースを最低1つは実装しているはずです。 候補となるインターフェースを順に問い合わせて行き、 そのインターフェースをコントロールが実装しているのであれば、 そのインターフェースのInitNewを呼び出してコントロールのデータを初期化できます。 各インターフェースにはLoadというメソッドも含まれており、 これを呼び出すことで任意のデータをロードさせることもできますが、 これには複雑な手順が生じることもあります。 たとえば、IPersistPropertyBag::Loadを呼び出す場合は、IPropertyBagを実装したオブジェクトを作成しておかなければならず、 さらにそのオブジェクトでコントロールのプロパティとして設定したい値を返さなければなりません。

IOleObject::GetMiscStatusやIOleObject::SetClientSiteの呼び出しは、 コントロールがIQuickActivateを実装している場合は省略できます。 次に、IQuickActivateを考慮する場合のコードを示します。

bQuickActivate = QuickActivate();

if (!bQuickActivate) {
	m_pOleObject->GetMiscStatus(DVASPECT_CONTENT, &m_dwMiscStatus);
	if (m_dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST)
		m_pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));
}

InitNew();

if (!bQuickActivate) {
	if (!(m_dwMiscStatus & OLEMISC_SETCLIENTSITEFIRST))
		m_pOleObject->SetClientSite(static_cast<IOleClientSite *>(this));
	SetEventSink();
}

bQuickActivateがFALSEであるかを確認しているのは、 QuickActivateが成功した場合にこれらの処理を行う必要がないからです。 QuickActivateとは、GetMiscStatus、SetClientSite、イベントシンクへの接続などを一括して行う機能であり、 これをコントロールがサポートしている場合は、QuickActivateを用いたコントロールの初期化が推奨されています。 QuickActivateという自作メソッドは次のようになっています。

BOOL CActiveXContainer::QuickActivate()
{
	HRESULT        hr;
	QACONTAINER    qaContainer;
	QACONTROL      qaControl;
	IQuickActivate *pQuickActivate;

	hr = m_pOleObject->QueryInterface(IID_PPV_ARGS(&pQuickActivate));
	if (FAILED(hr))
		return FALSE;

	ZeroMemory(&qaContainer, sizeof(QACONTAINER));
	qaContainer.cbSize        = sizeof(QACONTAINER);
	qaContainer.pClientSite   = static_cast<IOleClientSite *>(this);
	qaContainer.pUnkEventSink = m_pEventSink;
	
	qaControl.cbSize = sizeof(QACONTROL);

	hr = pQuickActivate->QuickActivate(&qaContainer, &qaControl);
	if (FAILED(hr)) {
		pQuickActivate->Release();
		return FALSE;
	}
	
	m_dwMiscStatus = qaControl.dwMiscStatus;
	m_dwEventCookie = qaControl.dwEventCookie;

	pQuickActivate->Release();

	return TRUE;
}

QueryInterfaceでIQuickActivateを取得できれば、オブジェクトはQuickActivateをサポートしていることになります。 QuickActivateはIQuickActivate::QuickActivateを呼び出すことで可能ですが、 このためにはコンテナの情報をQACONTAINER構造体に格納しておく必要があります。 この構造体に指定できる情報は様々ですが、今回はサイトとイベントシンク(詳細は次節)を指定するようにしています。 IQuickActivate::QuickActivateが成功すれば、第2引数のQACONTROL構造体にコントロールの情報が格納されます。 情報ステータスとクッキー(コントロールとの接続を識別する値)は後で必要になるため、メンバ変数に保存しておきます。

コントロールの初期化が完了したら、コンテナのウインドウにインプレースアクティベーションするようにします。

if (m_dwMiscStatus & OLEMISC_ALWAYSRUN) {
	m_pOleObject->QueryInterface(IID_PPV_ARGS(&pRunnableObject));
	if (pRunnableObject != NULL) {
		pRunnableObject->Run(NULL);
		pRunnableObject->Release();
	}
}

SetRectEmpty(&rc);
hr = m_pOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, static_cast<IOleClientSite *>(this), 0, m_hwnd, &rc);
if (FAILED(hr))
	return FALSE;

情報スタータスにOLEMISC_ALWAYSRUNが含まれている場合は、 IRunnableObject::Runを呼び出してコントロールを実行状態にすべきとされています。 実行状態というのは、コントロールのイメージがコンテナのウインドウに描画された状態のことを指し、 これがアクティブ状態になった場合は、実際にコントロールのウインドウがコンテナ上に表示されます。 インプレースアクティベーションを行うというのは、正にコントロールをアクティブ状態にすることを意味し、 IOleObject::DoVerbにOLEIVERB_INPLACEACTIVATEを指定することで可能になります。 本来ならばこのように自動でインプレースアクティベーションを行ってよいのは、 情報スタータスにOLEMISC_ACTIVATEWHENVISIBLEが含まれている場合に限られると思われますが、 今回はそれを考慮していません。 もし、自動でインプレースアクティベーションを行わない場合は、 WM_PAINTでOleDrawを呼び出して描画したコントロールのイメージにマウスが侵入した場合や、 マウスがクリックされた場合にインプレースアクティベーションを行えばよいでしょう。 ちなみに、DoVerbに指定したRECT構造体はSetRectEmptyで0に初期化されているため、 DoVerbが制御を返した時点ではコントロールの表示を確認できません。

コンテナのウインドウプロシージャは次のようになっています。

LRESULT CActiveXContainer::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_CREATE:
		m_hwnd = hwnd;
		if (!Create())
			return -1;
		return 0;

	case WM_SIZE: {
		RECT              rc = {0, 0, LOWORD(lParam), HIWORD(lParam)};
		IOleInPlaceObject *pOleInPlaceObject;
		
		if (m_dwMiscStatus & OLEMISC_INVISIBLEATRUNTIME)
			return 0;

		m_pOleObject->QueryInterface(IID_PPV_ARGS(&pOleInPlaceObject));
		pOleInPlaceObject->SetObjectRects(&rc, &rc);
		pOleInPlaceObject->Release();
		return 0;
	}

	case WM_DESTROY:
		if (m_pConnectionPoint != NULL) {
			m_pConnectionPoint->Unadvise(m_dwEventCookie);
			m_pConnectionPoint->Release();
		}
		
		if (m_pOleObject != NULL)
			m_pEventSink->Release();
		
		if (m_pOleObject != NULL) {
			RECT rc;
			SetRectEmpty(&rc);
			m_pOleObject->DoVerb(OLEIVERB_HIDE, NULL, NULL, 0, m_hwnd, &rc);
			m_pOleObject->Close(OLECLOSE_NOSAVE);
			m_pOleObject->SetClientSite(NULL);
			m_pOleObject->Release();
		}
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_SIZEではコントロールのウインドウサイズをコンテナのサイズに合せるために、 IOleInPlaceObject::SetObjectRectsを呼び出しています。 ただし、情報ステータスにOLEMISC_INVISIBLEATRUNTIMEが含まれている場合は、 コントロールを表示すべきないことを意味しているため、サイズ変更を行わないようにしています。 表示を想定しないコントロールを開発することに意味があるとのかと思うかもしれませんが、 たとえばタイマのような動作をするコントロールを開発する場合は必ずしも表示が必要とは限りません。 WM_DESTROYではコントロールと確立した接続を破棄し、続いてイベントシンクも開放しています。 最後にコントロールを開放するために、IOleObject::DoVerb(OLEIVERB_HIDE)でインプレースアクティベーションを解除し、 IOleObject::Closeでウインドウを破棄します。


戻る