1
3

今更COMとATLを勉強する: aggregateできないクラス

Last updated at Posted at 2023-10-18

基本構成

プロセスの壁は跨がず、DLLをregsvr32コマンドでレジストリに登録しておき、それをCoCreateInstanceする。まあつまりインプロセスサーバー。

まずはATLを使わない例

のコードを動かして遊んでみる。

// ヘッダファイルのインクルード
#include <windows.h>	// 標準WindowsAPI
#include <cstdio>		// 標準入出力
#include <tchar.h>		// TCHAR対応
#include <iostream>
#include "IUnknown_.h"	// MIDL生成
#include "IUnknown__i.c"	// GUID

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	IUnknown_* pUnknown = NULL;

	CoInitialize(NULL);

	std::cout << "call CoCreateInstance" << std::endl;
	HRESULT hr = CoCreateInstance(CLSID_CUnknown, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown_, (void **)&pUnknown);
	if (FAILED(hr)){
		_tprintf(_T("CoCreateInstance failed!\n"));
		CoUninitialize();
		return 0;
	}
	std::cout << "call pUnknown->Method" << std::endl;
	pUnknown->Method();
	std::cout << "call pUnknown->Release" << std::endl;
	pUnknown->Release();

	CoUninitialize();

	// プログラムの終了
	return 0;

}
call CoGetClassObject
CClassFactory::CClassFactory, m_lRef=1
CClassFactory::QueryInterface!
CClassFactory::AddRef, m_lRef=2
CClassFactory::AddRef, m_lRef=3
CClassFactory::Release, m_lRef=2
CClassFactory::QueryInterface!
CClassFactory::AddRef, m_lRef=3
CClassFactory::Release, m_lRef=2
call pcf->CreateInstance
CClassFactory::CreateInstance
CUnknown::QueryInterface!
CUnknown::AddRef, m_lRef=2
CUnknown::Release, m_lRef=1
call pcf->Release
CClassFactory::Release, m_lRef=1
call pUnknown->Method
CUnknown::Method!
call pUnknown->Release
CUnknown::Release, m_lRef=0
CClassFactory::~CClassFactory, m_lRef=1

image.png

image.png

image.png

さて、ここで、

$$ CoCreateInstance = CoGetClassObject + CreateInstance + Release $$

となるはずなので試してみる。

// ヘッダファイルのインクルード
#include <windows.h>	// 標準WindowsAPI
#include <cstdio>		// 標準入出力
#include <tchar.h>		// TCHAR対応
#include <iostream>
#include "IUnknown_.h"	// MIDL生成
#include "IUnknown__i.c"	// GUID

// _tmain関数の定義
int _tmain(int argc, TCHAR *argv[]){	// main関数のTCHAR版.

	IUnknown_* pUnknown = NULL;

	CoInitialize(NULL);

	std::cout << "call CoCreateInstance" << std::endl;
	HRESULT hr = CoCreateInstance(CLSID_CUnknown, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown_, (void **)&pUnknown);
	if (FAILED(hr)){
		_tprintf(_T("CoCreateInstance failed!\n"));
		CoUninitialize();
		return 0;
	}
	std::cout << "call pUnknown->Method" << std::endl;
	pUnknown->Method();
	std::cout << "call pUnknown->Release" << std::endl;
	pUnknown->Release();

	CoUninitialize();

	// プログラムの終了
	return 0;

}
call CoCreateInstance
CClassFactory::CClassFactory, m_lRef=1
CClassFactory::QueryInterface!
CClassFactory::AddRef, m_lRef=2
CClassFactory::CreateInstance
CUnknown::QueryInterface!
CUnknown::AddRef, m_lRef=2
CUnknown::Release, m_lRef=1
CUnknown::AddRef, m_lRef=2
CUnknown::Release, m_lRef=1
CClassFactory::Release, m_lRef=1
CUnknown::QueryInterface!
CUnknown::AddRef, m_lRef=2
CUnknown::Release, m_lRef=1
call pUnknown->Method
CUnknown::Method!
call pUnknown->Release
CUnknown::Release, m_lRef=0
CClassFactory::~CClassFactory, m_lRef=1

difffで比較するとこんな感じ。

image.png

CoCreateInstanceした場合は、CClassFactoryの参照カウント操作がサーバー側で行われるため、CoGetClassObject + CreateInstance + Releaseで2通信するのに比べ、1通信で済んでいることがわかる。

ATLで書いてみる

ATL初心者なのでとりあえず手元に転がってたVisualStudio2015でポチポチとATLのプロジェクトを作った。

image.png

image.png

image.png

image.png

image.png

image.png

// IClassFactory2.idl : IClassFactory2 の IDL ソース
//

// このファイルは、タイプ ライブラリ (IClassFactory2.tlb) およびマーシャリング コードを
// 作成するために MIDL ツールによって処理されます。

import "oaidl.idl";
import "ocidl.idl";

[
	object,
	uuid(4670985E-D857-46F5-9FFB-83B5032852DA),
	dual,
	nonextensible,
	pointer_default(unique)
]
interface ICUnknown : IDispatch{
	HRESULT Method(void);
};
[
	uuid(ACCD544C-42AC-4A37-9300-DEBE5626ADE7),
	version(1.0),
]
library IClassFactory2Lib
{
	importlib("stdole2.tlb");
	[
		uuid(4F1586EA-BF58-423D-81D9-19DD6248DFE4)		
	]
	coclass CUnknown
	{
		[default] interface ICUnknown;
	};
};
// CCUnknown

class ATL_NO_VTABLE CCUnknown :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CCUnknown, &CLSID_CUnknown>,
	public IDispatchImpl<ICUnknown, &IID_ICUnknown, &LIBID_IClassFactory2Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CCUnknown();

DECLARE_REGISTRY_RESOURCEID(IDR_CUNKNOWN)

DECLARE_NOT_AGGREGATABLE(CCUnknown)

BEGIN_COM_MAP(CCUnknown)
	COM_INTERFACE_ENTRY(ICUnknown)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

	//DECLARE_PROTECT_FINAL_CONSTRUCT()
	void InternalFinalConstructAddRef();
	void InternalFinalConstructRelease();

	HRESULT FinalConstruct();

	void FinalRelease();

public:

	STDMETHODIMP Method() override;


};

OBJECT_ENTRY_AUTO(__uuidof(CUnknown), CCUnknown)
// CUnknown.cpp : CCUnknown の実装

#include "stdafx.h"
#include "CUnknown.h"
#include <iostream>

// CCUnknown

CCUnknown::CCUnknown()
{
	std::cout << "CCUnknown::CCUnknown" << std::endl;
}


//DECLARE_PROTECT_FINAL_CONSTRUCT()

void CCUnknown::InternalFinalConstructAddRef()
{
	std::cout << "CCUnknown::InternalFinalConstructAddRef" << std::endl;
	InternalAddRef();
}

void CCUnknown::InternalFinalConstructRelease()
{
	std::cout << "CCUnknown::InternalFinalConstructRelease" << std::endl;
	InternalRelease();
}

HRESULT CCUnknown::FinalConstruct()
{
	std::cout << "CCUnknown::FinalConstruct" << std::endl;
	return S_OK;
}

void CCUnknown::FinalRelease()
{
	std::cout << "CCUnknown::FinalRelease" << std::endl;
}

STDMETHODIMP CCUnknown::Method()
{
	std::cout << "CCUnknown::Method" << std::endl;
	return S_OK;
}

これをATLを使わないプロジェクトから先程と同様に呼び出してみる。めんどくさいのでCoGetClassObject+CreateInstance+Relase版とCoCreateInstance版は#ifで切り替えることにした。

// Main.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include  <IClassFactory2_i.h>
#include <IClassFactory2_i.c>
#include <iostream>
int main()
{
	ICUnknown* pUnknown = NULL;

	CoInitialize(NULL);

#if 1
	IClassFactory* pcf = NULL;
	std::cout << "call CoGetClassObject" << std::endl;
	HRESULT hr1 = CoGetClassObject(CLSID_CUnknown, CLSCTX_INPROC_SERVER, 0, IID_IClassFactory, reinterpret_cast<void**>(&pcf));
	if (FAILED(hr1)) {
		_tprintf(_T("CoGetClassObject failed!\n"));
		CoUninitialize();
		return 0;
	}
	std::cout << "call pcf->CreateInstance" << std::endl;
	HRESULT hr2 = pcf->CreateInstance(NULL, IID_ICUnknown, reinterpret_cast<void**>(&pUnknown));
	std::cout << "call pcf->Release" << std::endl;
	pcf->Release();
	if (FAILED(hr2)) {
		_tprintf(_T("CreateInstance failed!\n"));
		CoUninitialize();
		return 0;
	}
#else
	std::cout << "call CoCreateInstance" << std::endl;
	HRESULT hr = CoCreateInstance(CLSID_CUnknown, NULL, CLSCTX_INPROC_SERVER, IID_ICUnknown, (void **)&pUnknown);
	if (FAILED(hr)) {
		_tprintf(_T("CoCreateInstance failed!\n"));
		CoUninitialize();
		return 0;
	}
#endif
	std::cout << "call pUnknown->Method" << std::endl;
	pUnknown->Method();
	std::cout << "call pUnknown->Release" << std::endl;
	pUnknown->Release();

	CoUninitialize();

	// プログラムの終了
	return 0;

}
call CoGetClassObject
DllGetClassObject
call pcf->CreateInstance
CCUnknown::CCUnknown
CCUnknown::InternalFinalConstructAddRef, m_dwRef=0
CCUnknown::FinalConstruct
CCUnknown::InternalFinalConstructRelease, m_dwRef=1
call pcf->Release
call pUnknown->Method
CCUnknown::Method
call pUnknown->Release
CCUnknown::FinalRelease

ちゃんと呼び出せている。

DECLARE_PROTECT_FINAL_CONSTRUCT

このマクロは次のように定義されている。

#define DECLARE_PROTECT_FINAL_CONSTRUCT()\
	void InternalFinalConstructAddRef() {InternalAddRef();}\
	void InternalFinalConstructRelease() {InternalRelease();}

今回はログを埋め込みたかったので同等の内容を手動で定義している。

ではこれを定義しない場合何が起こるのだろうか。

今回作成したCCUnknownクラスの継承関係は次のようになっている。

つまりCComObjectRootBaseで定義されている次の定義が用いられるはずだ。

	void InternalFinalConstructAddRef() 
	{
	}
	void InternalFinalConstructRelease()
	{
		ATLASSUME(m_dwRef == 0);
	}

実験しよう。ログを埋め込みたいので明示的に基底クラスの関数を呼び出すようにしてみる

void CCUnknown::InternalFinalConstructAddRef()
{
	std::cout << "CCUnknown::InternalFinalConstructAddRef, m_dwRef=" << m_dwRef << std::endl;
	base::InternalFinalConstructAddRef();
}

void CCUnknown::InternalFinalConstructRelease()
{
	std::cout << "CCUnknown::InternalFinalConstructRelease, m_dwRef=" << m_dwRef << std::endl;
	base::InternalFinalConstructRelease();
}

結果は次の通り。

call CoGetClassObject
DllGetClassObject
call pcf->CreateInstance
CCUnknown::CCUnknown
CCUnknown::InternalFinalConstructAddRef, m_dwRef=0
CCUnknown::FinalConstruct
CCUnknown::InternalFinalConstructRelease, m_dwRef=0
call pcf->Release
call pUnknown->Method
CCUnknown::Method
call pUnknown->Release
CCUnknown::FinalRelease

image.png

うーん?

DECLARE_NOT_AGGREGATABLE vs DECLARE_POLY_AGGREGATABLE

試しにDECLARE_POLY_AGGREGATABLEに切り替えてみる

call CoGetClassObject
DllGetClassObject
call pcf->CreateInstance
CCUnknown::CCUnknown
CCUnknown::FinalConstruct
call pcf->Release
call pUnknown->Method
CCUnknown::Method
call pUnknown->Release
CCUnknown::FinalRelease

あれ、InternalFinalConstructAddRef/InternalFinalConstructReleaseはいずこに・・・?

image.png

image.png

image.png

image.png

どうもCCUnknownInternalFinalConstructAddRef/InternalFinalConstructReleaseはすっ飛ばされている感じがある。

#define DECLARE_POLY_AGGREGATABLE(x) public:\
	typedef ATL::CComCreator< ATL::CComPolyObject< x > > _CreatorClass;

より、CComCreatorのCreateInstanceが呼ばれ、

template <class T1>
class CComCreator
{
public:
	static HRESULT WINAPI CreateInstance(
		_In_opt_ void* pv, 
		_In_ REFIID riid, 
		_COM_Outptr_ LPVOID* ppv)
	{
		ATLASSERT(ppv != NULL);
		if (ppv == NULL)
			return E_POINTER;
		*ppv = NULL;

		HRESULT hRes = E_OUTOFMEMORY;
		T1* p = NULL;

ATLPREFAST_SUPPRESS(6014 28197)
		/* prefast noise VSW 489981 */
		ATLTRY(p = _ATL_NEW T1(pv))
ATLPREFAST_UNSUPPRESS()

		if (p != NULL)
		{
			p->SetVoid(pv);
			p->InternalFinalConstructAddRef();
			hRes = p->_AtlInitialConstruct();
			if (SUCCEEDED(hRes))
				hRes = p->FinalConstruct();
			if (SUCCEEDED(hRes))
				hRes = p->_AtlFinalConstruct();
			p->InternalFinalConstructRelease();
			if (hRes == S_OK)
			{
				hRes = p->QueryInterface(riid, ppv);
				_Analysis_assume_(hRes == S_OK || FAILED(hRes));
			}
			if (hRes != S_OK)
				delete p;
		}
		return hRes;
	}
};

T1CComPolyObject<CCUnknown>であるから

CCUnknownではなく、継承関係をたどったCComObjectRootBaseのそれがよびだされるのであろう。

整理すると、DECLARE_POLY_AGGREGATABLEを指定した場合、対象クラスにDECLARE_PROTECT_FINAL_CONSTRUCTを指定しても効果は発揮されないということが言える。

続編

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3