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

## 基本構成

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

https://www.keicode.com/winprimer/com-basics1-3-registry.php

## まずはATLを使わない例

https://bg1.hatenablog.com/entry/2017/11/08/210000

https://github.com/bg1bgst333/Sample/tree/master/com/IClassFactory/IClassFactory/src/IClassFactory

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

```cpp
// ヘッダファイルのインクルード
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/8ec44959-0a21-471d-b4b3-4b50f93cd60f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/06c07695-8cac-4764-cfa6-9bcde8cc749b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/1ae25d88-588e-c9af-ed90-842c280a47a9.png)

さて、ここで、

$$ CoCreateInstance = CoGetClassObject + CreateInstance + Release $$

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

```cpp
// ヘッダファイルのインクルード
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/73ace87f-eb86-238a-73ac-e92aff114761.png)

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

## ATLで書いてみる

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/261e3140-2496-98d1-c20b-962a7e994f27.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/8aaafe05-7cfc-2eec-3474-31ce0368f3fe.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/2168f7aa-1b9b-91b9-8a24-357fa513006c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/b1e79b10-55f4-c676-b708-ef8e3d9a89d9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/de606ac0-a440-844e-32f1-772e1df3f4bf.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/1b747e27-4371-8b8e-b2f7-e78665a56b14.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;
	};
};
```

```cpp
// 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)
```

```cpp
// 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`で切り替えることにした。

```cpp
// 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`

このマクロは次のように定義されている。
```cpp
#define DECLARE_PROTECT_FINAL_CONSTRUCT()\
	void InternalFinalConstructAddRef() {InternalAddRef();}\
	void InternalFinalConstructRelease() {InternalRelease();}
```

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

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

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

```plantuml
@startuml
class CComObjectRootBase #aliceblue;line:blue; {
  InternalFinalConstructAddRef(): void
  InternalFinalConstructRelease(): void
}
CComObjectRootBase <|-- CComObjectRootEx
CComObjectRootEx <|-- CCUnknown
CComCoClass <|-- CCUnknown
ICUnknown <|-- IDispatchImpl
IDispatchImpl <|-- CCUnknown

@enduml
```

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

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

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

```cpp
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/ad4425f9-c68c-49f6-74f0-94feb3659fda.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/416f41af-2290-183a-cbdd-7955aec71736.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/5f460159-3b96-7ebd-23a5-82638944e656.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/03b6fd12-dfff-0801-ed71-d5043b375e09.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94177/26993f8c-90fb-cf60-8b77-3a76d9b90a83.png)

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

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

より、`CComCreator`のCreateInstanceが呼ばれ、

```cpp
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;
	}
};
```

`T1`は`CComPolyObject<CCUnknown>`であるから

```plantuml
@startuml
class CComObjectRootBase #aliceblue;line:blue; {
  InternalFinalConstructAddRef(): void
  InternalFinalConstructRelease(): void
}
CComObjectRootBase <|-- CComObjectRootEx
CComObjectRootEx <|-- CComPolyObject


@enduml
```

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

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

# 続編

https://qiita.com/yumetodo/items/f961bc96594b9b5619c2

https://tech-blog.optim.co.jp/entry/2024/03/28/100000