Visual Studio 2017 Visual C++ による ATL COM DLL の作成

#はじめに
前に [「Visual Studio 2017 Visual C++ による ATL の基本」](https://qiita.com/tadnakam/items/7ce3dc76ba66892231d8) という投稿を行いましたが、かなり前に作った原稿なので Visual Studio 2017 Update 3 (v15.3) より前のバージョンで作成しています。

Update 3 において、画面等の操作性がかなり変更になっており、もしかしたら現在の Update 5 と異なっているかもしれません。

そこで新たに ATL を使って COM DLL のサンプル BitField2.dll を作成してみました。

#サンプル AxBitField2.dll の仕様
##概要
AxBitField2.dll は 32bit ワードをビット単位で扱う COM DLL です。ワードの一部をビット、バイト、16ビットワード単位で書き換えできます。指定されたビットフィールド。バイト、16ビットワードの読み出しもできます。


##名称
- インターフェース名: IBitField
- インターフェース ID (IID): 459d383d-5bd4-4beb-83ee-48ca15bfe317
- ライブラリ名: AxBitField2Lib
- ライブラリ ID: e188ca82-11fe-41ef-bae5-4c51d103fa30
- クラス名: BitField
- クラス ID (CLSID): c1a7d0f-29b0-415e-9c3d-cd2486369f39

##プロパティ
- long LongWord (get / set) 32 bit のワードを取得/設定する。(* 64 bit は long long 型)
- short SubWord (get / set) GetBitsEx メソッドで使用する16 bit のワードを取得/設定する。

##メソッド
- void Endian(): バイト並びを逆にする。
- long GetBits(short p, short n): ビット指定位置 p から n ビット分のビットフィールドを切り出す。
- long GetBitsEx(short p, short n): GetBits と同じだが、ビットフィールドの範囲を超えたら SubWord を使って補完する。
- short GetByte(short p): バイト指定位置 p のバイトを取得する。(p = 0, 1, 2, 3)
- short GetWord(short p): 16ビットワード指定位置 p のバイトを取得する。(p = 0, 1)
- void SetBits(short p, short n, long data): ビット指定位置 p から n ビット分のビットフィールドを書き換える。
- void SetByte(short p, short data): 指定位置のバイトを書き換える。(p = 0, 1, 2, 3)
- void SetWord(short p, short data): 指定位置の16ビットワードを書き換える。(p = 0, 1)

#プロジェクトの作成
「新しいプロジェクト」ダイアログを開いて、「ATL プロジェクト」を選んで OK ボタンをクリックします。

![VCProj_ATL.png](https://qiita-image-store.s3.amazonaws.com/0/62802/6ab3ff93-415c-c39a-bbb2-5bce5cc75ba7.png)
Fig.1 「新しいプロジェクト」ダイアログ

Fig.2 のようなダイアログが開くのでプロジェクトのオプションを設定します。アプリケーションの種類は、DLL のままとし、その他もそのままとします。

![ATLProject5.png](https://qiita-image-store.s3.amazonaws.com/0/62802/28138a3f-85fd-aba6-670c-3001c4e5f85f.png)
Fig.2 ATL プロジェクトのオプション

ソリューションエクスプローラでこのプロジェクトを選択して、「追加」メニューで「新しい項目の追加」を実行します。Fig.3 のようなダイアログが開いたら、 「ATL シンプルオブジェクト」を選択し、名前を適切なものに変更して「追加」ボタンをクリックします。この名前は クラスファイルの名称で、デフォルトのままだと DLL に含まれるクラス名 (ただし、"C" が先頭に付加される) になります。

![ATLNewProject5.png](https://qiita-image-store.s3.amazonaws.com/0/62802/7fca0e44-2b1e-a3dd-eb1a-29823b872e9b.png)

Fig.3 「新しい項目の追加」ダイアログ

次に Fig.4 のようなダイアログが開きます。必要なら名前を変更できますが、たいていはこのままでいいので「次へ」ボタンをクリックします。

![SimpleObject5.png](https://qiita-image-store.s3.amazonaws.com/0/62802/80c76f75-a165-b9f0-4ab9-23a3e2244f60.png)
Fig.4 「名前の入力」ダイアログ

次に、Fig.5 のようなダイアログが開きますが、これはこのままでいいので「次へ」ボタンをクリックします。

![SimpleObject5-1.png](https://qiita-image-store.s3.amazonaws.com/0/62802/df81ed1a-3631-8c8d-5942-fcf27f945315.png)
Fig.5 「ファイルの種類のオプション」ダイアログ

次に、Fig.6 のようなダイアログが開きますが、これもこのままでいいので「完了」ボタンをクリックします。

![SimpleObject5-2.png](https://qiita-image-store.s3.amazonaws.com/0/62802/00fe35af-5857-4676-c9ce-be6d5be2e809.png)
Fig.6 最後のダイアログ

最後に、プロジェクトに関連クラス等が追加されます。

![AxBitField2_ProjTree.png](https://qiita-image-store.s3.amazonaws.com/0/62802/75c33017-2224-8eca-283d-e9f8e6a7da9a.png)
Fig.7 プロジェクトに含まれるファイル

#プロパティの追加
- ソリューションエクスプローラで当該プロジェクトを選択し、クラスビューを表示する。
- ビューツリーでインターフェースを選択する。この例では IBitField を選ぶ。(複数あるが最後のもの)
- コンテキストメニューを開き、「追加」/「プロパティの追加」を実行する。
- Fig.8 のようなダイアログが開くのでプロパティの種類と名称などを入力して、次へをクリックする。
- Fig.9 のようなダイアログが開くので、必要なら項目を入力して「完了」ボタンをクリックする。
- プロパティがコードに追加される。(.idl, .h, .cpp ファイル)
- (注意) 間違ってしまった場合は手動でコードを修正する。

![IBitField_AddProp.png](https://qiita-image-store.s3.amazonaws.com/0/62802/9a59937c-5e9d-02d6-f81c-39b5920fa0ea.png)
Fig.8 プロパティの追加ウィザード

![IBitField_AddProp2.png](https://qiita-image-store.s3.amazonaws.com/0/62802/c4688ade-2982-706a-e063-54d8b6dc64ce.png)
Fig.9 プロパティの追加ウィザード (IDL 属性)

#メソッドの追加
- ソリューションエクスプローラで当該プロジェクトを選択し、クラスビューを表示する。
- ビューツリーでインターフェースを選択する。この例では IBitField を選ぶ。(複数あるが最後のもの)
- コンテキストメニューを開き、「追加」/「メソッドの追加」を実行する。
- Fig.10 のようなダイアログが開くのでプロパティの種類と名称などを入力して、次へをクリックする。
- IDL 属性入力ダイアログが開くので、必要なら項目を入力して「完了」ボタンをクリックする。
- メソッドがコードに追加される。(.idl, .h, .cpp ファイル)
- (注意) 間違ってしまった場合は手動でコードを修正する。

![IBitField_AddMethod.png](https://qiita-image-store.s3.amazonaws.com/0/62802/1e46a2fc-62ce-9b28-0baf-74dd07e20fde.png)
Fig.10 メソッドの追加ウィザード

プロパティとメソッドの追加が終わったら、それらの具体的なコードを記述してビルドを行う。

#ビルトと登録
ビルドでエラーがなくなったら、DLL と TLB (Type Library) ができているはずなので、適切なフォルダへコピーして、改めて regsvr32 によりレジストリに DLL (COM オブジェクト) を登録する。

ここでは c:\lib に AxBitField2.dll と AxBitField2.tlb をコピーして登録する例を示す。

> C:\lib> regsvr32 AxBitField2.dll


#テスト (COM DLL の使用例)
下にテストプログラムの例を示します。

```C++
// TestAxBitField2.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#import "C:\lib\AxBitField2.tlb" named_guids no_namespace

int main()
{
	// COM の初期化のため必要
	CoInitialize(NULL);

	// COM オブジェクトを定義
	CComPtr<IBitField> pObj;

	// COM オブジェクトを作成
	HRESULT hr = pObj.CoCreateInstance(__uuidof(BitField));
	
	// メソッドとプロパティの使用

	// LongWord
	pObj->LongWord = 0x01020304;
	printf_s("%08x\n", pObj->LongWord);
	pObj->Endian();
	printf_s("Endian: %08x\n", pObj->LongWord);

	// SetBits
	pObj->SetBits(7, 4, 0xb);
	printf_s("SetBits: %08x\n", pObj->LongWord);

	// SetByte
	pObj->SetByte(1, 0xbc);
	printf_s("SetByte: %08x\n", pObj->LongWord);

	// GetByte
	printf_s("%02x\n", pObj->GetByte(0));
	printf_s("%02x\n", pObj->GetByte(1));
	printf_s("%02x\n", pObj->GetByte(2));
	printf_s("%02x\n", pObj->GetByte(3));

	// SetWord
	pObj->SetWord(0, 0xc0d0);
	printf_s("SetWord: %08x\n", pObj->LongWord);

	// GetWord
	printf_s("%04x\n", pObj->GetWord(1));
	printf_s("%04x\n", pObj->GetWord(2));

	// GetBits
	printf_s("%01x\n", pObj->GetBits(7, 4));
	printf_s("%01x\n", pObj->GetBits(6, 4));
	printf_s("%01x\n", pObj->GetBits(5, 4));

	// SubWord
	pObj->SubWord = 0xffff;
	printf_s("SubWord: %08x\n", pObj->SubWord);

	// GetBitsEx
	printf_s("%08x\n", pObj->GetBitsEx(10, 6));

	getchar();
	return 0;
}
```

#AxBitField2 のコード
以下に AxBitField2 プログラムのコードを示します。

##AxBitField2.idl

```C++
// AxBitField2.idl : AxBitField2 の IDL ソース
//

// このファイルは、タイプ ライブラリ ([!output SAFE_IDL_NAME].tlb) およびマーシャリング コードを
// タイプ ライブラリ (AxBitField2.tlb) とマーシャリング コードを生成します。

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

[
	object,
	uuid(459d383d-5bd4-4beb-83ee-48ca15bfe317),
	dual,
	nonextensible,
	pointer_default(unique)
]
interface IBitField : IDispatch
{
	[propget, id(1)] HRESULT LongWord([out, retval] ULONG* pVal);
	[propput, id(1)] HRESULT LongWord([in] ULONG newVal);
	[propget, id(2)] HRESULT SubWord([out, retval] USHORT* pVal);
	[propput, id(2)] HRESULT SubWord([in] USHORT newVal);
	[id(3)] HRESULT Endian();
	[id(4)] HRESULT GetBits([in] SHORT b, [in] SHORT n, [out, retval] ULONG* data);
	[id(5)] HRESULT GetByte([in] SHORT b, [out, retval] USHORT* data);
	[id(6)] HRESULT GetWord([in] SHORT w, [out, retval] USHORT* data);
	[id(7)] HRESULT SetBits([in] SHORT b, [in] SHORT n, [in] ULONG data);
	[id(8)] HRESULT SetByte([in] SHORT b, [in] USHORT data);
	[id(9)] HRESULT SetWord([in] SHORT w, [in] USHORT data);
	[id(10)] HRESULT GetBitsEx([in] SHORT b, [in] SHORT n, [out, retval] ULONG* data);
};
[
	uuid(e188ca82-11fe-41ef-bae5-4c51d103fa30),
	version(1.0),
]
library AxBitField2Lib
{
	importlib("stdole2.tlb");
	[
		uuidd(c1a7d0f-29b0-415e-9c3d-cd2486369f39)
	]
	coclass BitField
	{
		[default] interface IBitField;
	};
};

import "shobjidl.idl";
```

##BitField.h

```C++
// BitField.h : CBitField の宣言

#pragma once
#include "resource.h"       // メイン シンボル

#include "AxBitField2_i.h"

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "DCOM の完全サポートを含んでいない Windows Mobile プラットフォームのような Windows CE プラットフォームでは、単一スレッド COM オブジェクトは正しくサポートされていません。ATL が単一スレッド COM オブジェクトの作成をサポートすること、およびその単一スレッド COM オブジェクトの実装の使用を許可することを強制するには、_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA を定義してください。ご使用の rgs ファイルのスレッド モデルは 'Free' に設定されており、DCOM Windows CE 以外のプラットフォームでサポートされる唯一のスレッド モデルと設定されていました。"
#endif

using namespace ATL;

// CBitField

class ATL_NO_VTABLE CBitField :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClasslt;CBitField, &CLSID_BitField>,
	public IDispatchImpllt;IBitField, &IID_IBitField, &LIBID_AxBitField2Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	ULONG m_longWord;
	WORD  m_subWord;
	BYTE  m_bytes[4];
	BYTE  m_subbytes[2];
	BYTE  m_bits[32];
	BYTE  m_subbits[16];

	CBitField()
	{
		short i;

		m_longWord = 0;
		m_subWord = 0;

		for (i = 0; i lt; 32; i++)
			m_bits[i] = 0;

		for (i = 0; i lt; 16; i++)
			m_subbits[i] = 0;

		for (i = 0; i lt; 4; i++)
			m_bytes[i] = 0;

		for (i = 0; i lt; 2; i++)
			m_subbytes[i] = 0;
	}

DECLARE_REGISTRY_RESOURCEID(106)

BEGIN_COM_MAP(CBitField)
	COM_INTERFACE_ENTRY(IBitField)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

private:
	void updateData();
public:
	STDMETHOD(get_LongWord)(ULONG* pVal);
	STDMETHOD(put_LongWord)(ULONG newVal);
	STDMETHOD(get_SubWord)(USHORT* pVal);
	STDMETHOD(put_SubWord)(USHORT newVal);
	STDMETHOD(Endian)();
	STDMETHOD(GetBits)(SHORT b, SHORT n, ULONG* data);
	STDMETHOD(GetByte)(SHORT b, USHORT* data);
	STDMETHOD(GetWord)(SHORT w, USHORT* data);
	STDMETHOD(SetBits)(SHORT b, SHORT n, ULONG data);
	STDMETHOD(SetByte)(SHORT b, USHORT data);
	STDMETHOD(SetWord)(SHORT w, USHORT data);
	STDMETHOD(GetBitsEx)(SHORT b, SHORT n, ULONG* data);
};

OBJECT_ENTRY_AUTO(__uuidof(BitField), CBitField)
```

##BitField.cpp

```
// BitField.cpp : CBitField の実装

#include "stdafx.h"
#include "BitField.h"


template<class T>
void swap(T& x, T& y)
{
	T w;
	w = x;
	x = y;
	y = w;
}

// CBitField

//
//  内部変数を更新する
//  ==================
void CBitField::updateData()
{
	short i;
	m_bytes[0] = static_cast<BYTE>(m_longWord & 0xff);
	m_bytes[1] = static_cast<BYTE>((m_longWord >> 8) & 0xff);
	m_bytes[2] = static_cast<BYTE>((m_longWord >> 16) & 0xff);
	m_bytes[3] = static_cast<BYTE>((m_longWord >> 24) & 0xff);
	m_subbytes[0] = static_cast<BYTE>(m_subWord & 0xff);
	m_subbytes[1] = static_cast<BYTE>((m_subWord >> 8) & 0xff);

	for (i = 0; i < 32; i++)
	{
		m_bits[i] = (static_cast<ULONG>(m_longWord) & (1 << i)) ? 1 : 0;
	}
	for (i = 0; i < 16; i++)
	{
		m_subbits[i] = (static_cast<ULONG>(m_subWord) & (1 << i)) ? 1 : 0;
	}
}

//
//   現在の32bitデータを得る
//   =======================
STDMETHODIMP CBitField::get_LongWord(ULONG* pVal)
{
	*pVal = static_cast<long>(m_longWord);
	return S_OK;
	return S_OK;
}

//
//   現在の32bitデータを設定する
//   ===========================
STDMETHODIMP CBitField::put_LongWord(ULONG newVal)
{
	m_longWord = static_cast<ULONG>(newVal);
	updateData();
	return S_OK;
	return S_OK;
}

//
//  サブワード(32ビット境界を超えてビットフィールドを取得するとき使う)
//  ===================================================================
STDMETHODIMP CBitField::get_SubWord(USHORT* pVal)
{
	*pVal = m_subWord;
	return S_OK;
}


STDMETHODIMP CBitField::put_SubWord(USHORT newVal)
{
	m_subWord = newVal;
	updateData();
	return S_OK;
}

//
//   バイト並びを変更する
//   ====================
STDMETHODIMP CBitField::Endian()
{
	swap(m_bytes[3], m_bytes[0]);
	swap(m_bytes[2], m_bytes[1]);
	m_longWord = m_bytes[3] << 24 | m_bytes[2] << 16 | m_bytes[1] << 8 | m_bytes[0];
	for (short i = 0; i < 32; i++)
	{
		m_bits[i] = (static_cast<ULONG>(m_longWord) & (1 << i)) ? 1 : 0;
	}
	return S_OK;
}


//
//   指定位置ｂと長さｎのビットフィールドを得る
//   ===========================================
STDMETHODIMP CBitField::GetBits(SHORT b, SHORT n, ULONG* data)
{
	short i, p;
	ULONG uData = 0;

	for (i = 0; i < n; i++)
	{
		p = b - n + i + 1;
		uData |= (m_bits[p] << i);
	}

	*data = static_cast<long>(uData);
	return S_OK;
}

//
//   指定位置のバイトを得る
//   ======================
STDMETHODIMP CBitField::GetByte(SHORT b, USHORT* data)
{
	*data = static_cast<short>(m_bytes[b]);
	return S_OK;
}

//
//   指定位置のワードを得る
//   ======================
STDMETHODIMP CBitField::GetWord(SHORT w, USHORT* data)
{
	switch (w)
	{
	case 0:
		*data = static_cast<short>(m_bytes[0]);
		break;

	case 1:
		*data = (static_cast<short>(m_bytes[1]) << 8) | static_cast<short>(m_bytes[0]);
		break;

	case 2:
		*data = (static_cast<short>(m_bytes[2]) << 8) | static_cast<short>(m_bytes[1]);
		break;

	case 3:
		*data = (static_cast<short>(m_bytes[3]) << 8) | static_cast<short>(m_bytes[2]);
		break;
	}
	return S_OK;
}


//
//   指定位置のビットフィールドを変更する
//   =====================================
STDMETHODIMP CBitField::SetBits(SHORT b, SHORT n, ULONG data)
{
	BYTE Bits[32];
	short i;

	// 与えられたデータをビットに分解
	for (i = 0; i < n; i++)
	{
		Bits[i] = (data & (1 << i)) ? 1 : 0;
	}

	// 元データの当該ビットを置き換え
	for (i = 0; i < n; i++)
	{
		m_bits[b - i] = Bits[n - i - 1];
	}

	// m_lonWord, m_bytesに反映
	m_longWord = 0;
	for (i = 0; i < 32; i++)
	{
		m_longWord |= ((m_bits[i] ? 1 : 0) << i);
	}

	m_bytes[0] = static_cast<BYTE>(m_longWord & 0xff);
	m_bytes[1] = static_cast<BYTE>((m_longWord >> 8) & 0xff);
	m_bytes[2] = static_cast<BYTE>((m_longWord >> 16) & 0xff);
	m_bytes[3] = static_cast<BYTE>((m_longWord >> 24) & 0xff);

	return S_OK;
}

//
//   指定位置のバイトを変更する
//   ==========================
STDMETHODIMP CBitField::SetByte(SHORT b, USHORT data)
{
	union {
		BYTE b[4];
		DWORD dw;
	} work;

	data &= 0xff;
	work.dw = m_longWord;

	switch (b)
	{
	case 0:
		work.b[0] = (BYTE)data;
		m_longWord = work.dw;
		break;

	case 1:
		work.b[1] = (BYTE)data;
		m_longWord = work.dw;
		break;

	case 2:
		work.b[2] = (BYTE)data;
		m_longWord = work.dw;
		break;

	case 3:
		work.b[3] = (BYTE)data;
		m_longWord = work.dw;
		break;
	}

	updateData();
	return S_OK;
}


//
//   指定位置のワードを変更する
//   ==========================
STDMETHODIMP CBitField::SetWord(SHORT w, USHORT data)
{
	union {
		WORD w[4];
		DWORD dw;
	} work;

	work.dw = m_longWord;

	if (w == 0)
	{
		// 下位ワード
		work.w[0] = data;
		m_longWord = work.dw;
	}
	else
	{
		// 上位ワード
		work.w[1] = data;
		m_longWord = work.dw;
	}

	updateData();

	return S_OK;
}

//
//  サブワードを使ったビット列抽出
//  ===============================
STDMETHODIMP CBitField::GetBitsEx(SHORT b, SHORT n, ULONG* data)
{
	short i, p;
	ULONG uData = 0;

	for (i = 0; i < n; i++)
	{
		p = b - n + i + 1;
		if (p >= 0)
		{
			uData |= (m_bits[p] << i);
		}
		else
		{
			uData |= (m_subbits[p + 16] << i);
		}
	}

	*data = static_cast<long>(uData);

	return S_OK;
}
```

-