Visual Studio 2017 Visual C++ による ATL の基本

#はじめに
ATL は Active Teplate Library の略で Visual C++ 用のテンプレートライブラリである。このライブラリは、主に COM オブジェクトの作成に使用される。(COM : Common Object Model)

ターゲットになる COM オブジェクトには、主に以下のようなものがある。

- COM Control: (例) VB6 用のコントロール。拡張子は .ocx 。
- COM DLL: (例) WSH で使用される WScript オブジェクトなど。
- COM EXE: (例) Excel や Word など。
- COM Control と COM EXE は現在ではそれほど使われないので、以下の説明は COM DLL を対象とする。COM DLL を作成すると、.NET 系の言語 (C#, VB.NET ..) から VC++ で作成した機能を簡単に利用できるようになる。

[Visual Studio 2017 Visual C++ による ATL COM DLL の作成](https://qiita.com/tadnakam/items/77ccb15c83a05b356b0c) 

#プロジェクトの作成
Visual Studio 2017 で ATL プロジェクトを開始するには、以下のような手順で行う。

(注意) COM DLL はレジストリを利用するので、Visual Studio は管理者モードで起動するか、手動で regsvr32 コマンドを実行して登録する必要がある。

(注意) 以下の画面は、Visual Studio 2017 Update 2 以前のものである。最新の Update を適用済みの Visual Studio 2017 画面は異なっているので注意すること。

- メニュー「ファイル / 新規作成 / プロジェクト」を実行して「新しいプロジェクト」ダイアログを開く。
- Visual C++ を選び、サブアイテムから ATL を選ぶ。
![NewProject.png](https://qiita-image-store.s3.amazonaws.com/0/62802/4a9a8ff7-cea6-568a-4ba6-c8f1b356721f.png)

- ATL プロジェクトウィザードが開くのでそのまま「完了」ボタンをクリックする。これにより ATL プロジェクトが生成される。
![ATLWizard.png](https://qiita-image-store.s3.amazonaws.com/0/62802/0de4cce4-0214-81d8-3124-10de57e81eba.png)


- メニュー「表示 / クラスビュー」を実行して、クラスビューを表示させる。
![ClassView.png](https://qiita-image-store.s3.amazonaws.com/0/62802/7d7c1830-eace-de56-204e-a1872c9f1c90.png)


#クラスの追加
クラスビューで「追加」メニューから「クラス」を選んで、「クラスの追加」ダイアログを開く。左側の一覧で VC++ を展開して ATL を選択する。

ATL のアイテム一覧が表示されるので、「ATL シンプルオブジェクト」を選び「追加」ボタンをクリックする。

![AddATLClass.png](https://qiita-image-store.s3.amazonaws.com/0/62802/5f0895a5-bbdb-7380-560c-e3bdf56db343.png)


「ATL シンプルオブジェクトウィザード」が表示されるので、必要項目を入力して「完了」ボタンをクリックする。これにより、この COM オブジェクトの機能を実装するクラスがプロジェクトに追加される。

![SimpleObjectWizard.png](https://qiita-image-store.s3.amazonaws.com/0/62802/62a6ee4b-abce-b599-6487-912c73658c19.png)


この例の場合、クラスビューは下のようになる。
![ClassView2.png](https://qiita-image-store.s3.amazonaws.com/0/62802/59114944-50b4-72c7-b93d-6620f0a5f52b.png)



#プロパティの追加
クラスビューでインターフェース(この例では IBStringClass)を選んで、追加メニューから「プロパティの追加」を選択する。プロパティ名と必要なパラメータを入力(普通は不要)して「完了」ボタンをクリックする。
![PropertyWizard.png](https://qiita-image-store.s3.amazonaws.com/0/62802/973c0171-d010-dba0-b09e-1f3172812b55.png)



#メソッドの追加
クラスビューでインターフェース(この例では IBStringClass)を選んで、追加メニューから「メソッドの追加」を選択する。メソッド名と必要なパラメータを入力して「完了」ボタンをクリックする。メソッドの戻り値は常に HRESULT であるので、実行結果を返す場合は out retval 属性を付加したパラメータを追加すること。
![MethodWizard.png](https://qiita-image-store.s3.amazonaws.com/0/62802/4b9ceaf5-580d-7bb3-0904-8738094fa19b.png)



この段階でクラスビューの IBStringClass インターフェースの内容は下のようになる。
![ClassView3.png](https://qiita-image-store.s3.amazonaws.com/0/62802/2112e3e0-a74a-f4f3-6e87-e9c4eeb2db1f.png)



この段階で一度ビルドしてみる。ビルドの設定は下のようになっていること。また、Visual Studio は管理者モードで起動されていること。
![Vs2017_Prop.png](https://qiita-image-store.s3.amazonaws.com/0/62802/3d78e03a-8f06-561c-cfd8-eade0178a2f4.png)



ビルドが成功すると、「正常終了」のメッセージが画面下に表示される。また、DLL ファイル (この例では、ATLProject1.dll) が ATLProject1\Debug フォルダに作成されているはずである。


#プロパティとメソッドの実装
先に追加したプロパティとメソッドに具体的なコードを追加していく。まず、Value プロパティであるが、BString オブジェクトの値(文字列)であるとする。また、 Append メソッドはパラメータで与えられた文字列を Value プロパティの内容に追加するメソッドとする。

メソッドとプロパティのコードは、この例で言うと BStringClass.cpp に含まれているので、具体的なコードをこれらのメソッドに追加していく。

*BStringClass.cpp*

```C++
// BStringClass.cpp : CBStringClass の実装

#include "stdafx.h"
#include "BStringClass.h"

// CBStringClass

// Append メソッド
STDMETHODIMP CBStringClass::Append(BSTR str, BSTR* result)
{
	m_Value.Append(str);
	*result = m_Value;
	return S_OK;
}

// Value プロパティ get アクセサ
STDMETHODIMP CBStringClass::get_Value(BSTR* pVal)
{
	*pVal = m_Value;
	return S_OK;
}

// Value プロパティ put アクセサ
STDMETHODIMP CBStringClass::put_Value(BSTR newVal)
{
	m_Value = newVal;
	return S_OK;
}
```

COM オブジェクトにコンストラクタやデストラクタはないが、それらに相当する FinalConstruct() と void FinalRelease() が BStringClass.h に含まれているので、必要なら初期化と破棄のコードを追加する。


*BStringClass.h*

```C++
// BStringClass.h : CBStringClass の宣言

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

#include "ATLProject1_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;

// CBStringClass

class ATL_NO_VTABLE CBStringClass :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CBStringClass, &CLSID_BStringClass>,
	public IDispatchImpl<IBStringClass, &IID_IBStringClass, &LIBID_ATLProject1Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
	CBStringClass()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_BSTRINGCLASS)

BEGIN_COM_MAP(CBStringClass)
	COM_INTERFACE_ENTRY(IBStringClass)
	COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	// 構築が完了するとき
	HRESULT FinalConstruct()
	{
		m_Value.Empty();
		return S_OK;
	}

	// 破棄が完了するとき
	void FinalRelease()
	{
	}

private:
	// このオブジェクトの値
	CComBSTR m_Value;

public:

	// メソッドとプロパティの宣言

	STDMETHOD(Append)(BSTR str, BSTR* result);
	STDMETHOD(get_Value)(BSTR* pVal);
	STDMETHOD(put_Value)(BSTR newVal);
};

OBJECT_ENTRY_AUTO(__uuidof(BStringClass), CBStringClass)
```

#テストプログラム
C# や VB.NET からこの COM オブジェクトを使用するのは簡単で、テストプログラムのプロジェクトの参照で「参照マネージャー」を開いて、COM の中からこの COM オブジェクトのタイプライブラリをチェックしてやれば使用可能になる。


##VB.NET の場合
下に簡単なテストプログラム (VB.NET) を示す。

```VB
''' <summary>
''' ATLProject1 のテスト
''' </summary>
Module Module1
    ''' <summary>
    ''' メインプログラム
    ''' </summary>
    Sub Main()
        Dim obj As ATLProject1Lib.IBStringClass = New ATLProject1Lib.BStringClass()
        obj.Value = "ABC"
        Console.WriteLine(obj.Value)
        Dim str = obj.Append("XYZ")
        Console.WriteLine(str)
#If DEBUG Then
        Console.ReadKey()
#End If
    End Sub
End Module
```

このテストプログラムの実行例を下に示す。
![Execute.png](https://qiita-image-store.s3.amazonaws.com/0/62802/90dbfc64-5298-5ace-eeb7-803db4e1ed90.png)




##C++ の場合
C++ でテストプログラムを作ると次のような感じになる。この例では、ATLProject1.dll はリリースビルドして C:\lib の下へタイプライブラリ (ATLProject1.tlb) とともに移動した。この場合、管理者モードで起動したコマンドプロンプトで次のコマンドを実行して、この COM オブジェクトの登録を変更する必要がある。

> regsvr32 ATLProject1.dll

*TestATL.cpp*

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

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

int main()
{
	char a;

	// オブジェクトの作成
	CoInitialize(NULL);
	CComPtr<IBStringClass> pBStr;
	HRESULT hr = pBStr.CoCreateInstance(__uuidof(BStringClass));
	if (FAILED(hr))
	{
		cout << "Failed to create COM object.\n";
		cin >> a;
		return -1;
	}
	
	pBStr->Value = "ABC";
	cout << pBStr->Value << endl;
	cout << pBStr->Append("XYZ") << endl;

	cout << "Done." << endl;
	cin >> a;
	return 0;
}
```

*stdafx.h*

```C++
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>

// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include <atlbase.h>
#include <atlcom.h>
#include <iostream>
using namespace std;
```

![ExecuteCpp.png](https://qiita-image-store.s3.amazonaws.com/0/62802/f8d65dbe-f92b-4bbc-daa0-6f801825e243.png)


#ご注意

この文書はしばらく前に作ったものなので、Visual Studio の操作画面が変更になっているかもしれません。
(マイナーチェンジで画面が変更になったところがあるらしい)

-