VC++ でエクセルを起動する (#import でタイプライブラリを読込む)

# 概要

エクセルには`COM`(Component Object Model) という機能を使って、外部のプログラムからセルに書き込んだり、セルの内容を読込んだりすることができる機構が備わっています。
外部プログラムの言語は問いません。`COM`をサポートしてればいいので、`C++`、`VB`、`C#`、`VBS`、`PowerShell`、`Python` 何でもかまいません。
ここでは`VC++ (MSVC)` で`#import`[^1]を使ってタイプライブラリを読込んで、エクセルを操作します。
タイプライブラリについては`Microsoft Docs`の[COM、DCOM、およびタイプ ライブラリ](https://docs.microsoft.com/ja-jp/windows/win32/midl/com-dcom-and-type-libraries#type-library)を参照して下さい。
なお、他にも`MFC`を使った方法、`MFC`も`#import`も使わない方法かありますが、ここでは触れません。
[^1]:`#import`は`VC++`に特有の構文なので、他のコンパイラでは使用できないです。

# エクセルを起動

まずエクセルを起動するだけのプログラムです。
`#import`でタイプライブラリを読込む時に、`libid`を使用しているので、エクセルのバージョンには依存しないはずです。
```C++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <comutil.h>

#pragma warning (disable:4192)
//Excelを操作するためのタイプライブラリを読みこむ
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

struct StartOle {
	StartOle() { CoInitialize(NULL); }					//COMを初期化
	~StartOle() { CoUninitialize(); }					//COMを閉じる
} _inst_StartOle;

int main(void)
{
	Excel::_ApplicationPtr excelApp;

	//---------------------------------------------------------
	//Excelの起動
	excelApp.CreateInstance(L"Excel.Application");

	if (excelApp) {
		excelApp->Visible[0] = VARIANT_TRUE;			//エクセルを表示する
		excelApp->DisplayAlerts[0] = VARIANT_FALSE;		//警告が出ないように

		//確認のために一時停止
		::Sleep(4 * 1000);
		excelApp->WindowState[0] = Excel::xlMinimized;
		std::cout << "エクセルの起動を確認:";
		std::string s;
		std::getline(std::cin, s);

		//-------------------------------------------------
		//Excelを閉じる
		excelApp->Quit();
		excelApp.Release();								//COM オブジェクトを解放
	} else {
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```
`#import`で`libid`を使用しない場合は次のように記述します。
```C++
#import "C:\Program Files (x86)\Microsoft Office\root\vfs\ProgramFilesCommonX86\Microsoft Shared\OFFICE16\MSO.DLL"
#import "C:\Program Files (x86)\Microsoft Office\root\vfs\ProgramFilesCommonX86\Microsoft Shared\VBA\VBA6\Vbe6ext.olb"
#import "C:\Program Files (x86)\Microsoft Office\root\office16\EXCEL.EXE"
```
直接ファイルパスを書いても、タイプライブラリを読込むことができますが、このようにするとエクセルのバージョンに依存してしまいます。
ファイルパスを調べる手間[^2]もかかるので、`libid`を使った方が簡単で確実です。
[^2]:とは言え`OLE/COM Object View`を使えば簡単に調べられます。

# エクセルのセルにデータを書き込む

さすがにこれだけではさみしいので (ワークブックを開くだけでしたら、`ShellExecute`のほうが簡単) 、セルにデータを書き込んでファイルに保存してみます。
保存したファイル (ワークブック) はデフォルトではおそらくドキュメント フォルダーにあります。(カレント フォルダーではないです)
```C++
#define  WIN32_LEAN_AND_MEAN
#define  _WIN32_WINNT _WIN32_WINNT_WIN7
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <comutil.h>

#pragma warning (disable:4192)
//Excelを操作するためのタイプライブラリを読みこむ
//Microsoft Office Object Library
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" no_auto_exclude auto_rename dual_interfaces
//Microsoft Visual Basic for Applications Extensibillity
#import "libid:0002E157-0000-0000-C000-000000000046" dual_interfaces
//Mcrosoft Excel Object Library
#import "libid:00020813-0000-0000-C000-000000000046" no_auto_exclude auto_search auto_rename dual_interfaces

struct StartOle {
	StartOle() { CoInitialize(NULL); }					//COMを初期化
	~StartOle() { CoUninitialize(); }					//COMを閉じる
} _inst_StartOle;

void dump_com_error(_com_error& e)
{
	std::cerr << "Com error!\n";
	std::cerr << "\tCode = " << std::setw(8) << std::hex << e.Error() << '\n';
	std::cerr << "\tCode meaning = " << e.ErrorMessage() << '\n';
	_bstr_t bstrSource(e.Source());
	_bstr_t bstrDescription(e.Description());
	LPCSTR	source = (LPCSTR)bstrSource;
	std::cerr << "\tSource = " << (source ? source : "(NULL)") << "\n";
	LPCSTR descript = (LPCSTR)bstrDescription;
	std::cerr << "\tDescription = " << (descript ? descript : "(NULL)") << "\n";
}

int main(void)
{
	Excel::_ApplicationPtr excelApp;

	//---------------------------------------------------------
	//Excelの起動
	excelApp.CreateInstance(L"Excel.Application");

	if (excelApp) {
		excelApp->Visible[0] = VARIANT_TRUE;			//エクセルを表示する
		excelApp->DisplayAlerts[0] = VARIANT_FALSE;		//警告が出ないように

		try {											//例外を捕捉
			//新規でのWorkBookを追加
			Excel::WorkbooksPtr workbooks = excelApp->Workbooks;
			Excel::_WorkbookPtr workbook = workbooks->Add();

			//-------------------------------------------------
			//セルにデータを書き込む
			//アクティブ・シートを取得
			Excel::_WorksheetPtr worksheet = workbook->ActiveSheet;

			//文字列を入力
			worksheet->Range["A1"][vtMissing]->Value2 = bstr_t("色は匂へど 散りぬるを");
			worksheet.Release();							//COM オブジェクトを解放

			//確認のために一時停止
			::Sleep(4 * 1000);
			excelApp->WindowState[0] = Excel::xlMinimized;
			std::cout << "エクセルの起動およびデータ入力を確認:";
			std::string s;
			std::getline(std::cin, s);

			//-------------------------------------------------
			//ワークブックを保存
			bstr_t filename{ "Sample01.xlsx" };
			workbook->SaveAs(							//ワークシートを保存
							filename,					//保存するファイル名
							vtMissing,					//ファイルのフォーマットを指定
							vtMissing,					//読み取りパスワードを設定
							vtMissing,					//書き込みパスワードを設定
							vtMissing,					//ファイルを開く時、読み取り専用で開くことを推奨するメッセージを表示するかどうかの設定
							vtMissing,					//バックアップ・ファイルを作成するかどうかの指定
							Excel::xlNoChange			//アクセスモードの指定
			);

			//ワークブックを閉じる
			workbook->Close();
			workbook.Release();
			workbooks.Release();
		}
		catch (_com_error& e) {							//例外処理
			dump_com_error(e);
		}

		//Excelを閉じる
		excelApp->Quit();
		excelApp.Release();								//COM オブジェクトを解放
	} else {
		std::cerr << "エクセルを起動できません\n";
	}

	std::cout << "テストプログラムを終了:";
	std::string s;
	std::getline(std::cin, s);
}
```
## オブジェクトとメソッドとプロパティ

「なんか`VBA`に似ているな」と思った人。かなり鋭いです。基本的にオブジェクト (ワークシートとかセルとか) やメソッドは`VBA`と同じようなものが使えますし、`C++`ではあまりなじみが無いプロパティも使えます。勿論、あくまでも`C++`なので、構文などは`VBA`とは違いますが。

簡単にプログラムの説明をします。
```
struct StartOle {
	StartOle() { CoInitialize(NULL); }					//COMを初期化
	~StartOle() { CoUninitialize(); }					//COMを閉じる
} _inst_StartOle;
```
`COM`ライブラリの関数を使用するプログラムは必ず`COM`の初期化が必要になります。
開始時に`CoInitialize`を呼び、終了するとき`CoUninitialize`を呼びます。
`StartOle`というクラスの変数`_inst_StartOle`を定義することで、プログラム開始時 (main関数の開始前) に確実にコンストラクターで初期化し、終了時にデストラクターでクローズ処理を行うようにします。

```C++
	//Excelの起動
	Excel::_ApplicationPtr excelApp;
	excelApp.CreateInstance(L"Excel.Application");
```
`_ApplicationPtr`がアプリケーションオブジェクトです。`VBA`の`Application`に相当します。
`VBA`では普通`Application`を明示することは無いですが、`VC++`では省略できないです。
`CreateInstance`でエクセルを起動しますが、注意して頂きたいのはエクセルは実行中とは別のプロセスで稼働すると言うことです。
別のプロセスなので終了処理を確実に行わないと、エクセルのプロセスが残ってしまうと言うことです。
とくに難しいことではないのですが、手順を確実に守る必要があります。

```C++
	//新規でのWorkBookを追加
	Excel::WorkbooksPtr workbooks = excelApp->Workbooks;
	Excel::_WorkbookPtr workbook = workbooks->Add();
	//アクティブ・シートを取得
	Excel::_WorksheetPtr worksheet = workbook->ActiveSheet;
```
`WorkbooksPtr`がワークブックコレクションで、`_WorkbookPtr`がワークブックオブジェクトです。
`VBA`ではアクティブシートに対する操作に関してはオブジェクトを省略できますが、`VC++`では省略できなので、`ActiveSheet`プロパティでアクティブシートオブジェクト (`_WorksheetPtr`) を取得して操作対象にします。
```C++
	//セルに文字列を書き込む
	worksheet->Range["A1"][vtMissing]->Value2 = bstr_t("色は匂へど 散りぬるを");
```
`Range`でセルを指定して`Value2`プロパティに代入することで、セルに書き込むことができます。
```
	//ワークブックを閉じる
	workbook->Close();
	workbook.Release();
	workbooks.Release();
```
```
	//Excelを閉じる
	excelApp->Quit();
	excelApp.Release();			//COM オブジェクトを解放

```
`Close`でワークブックを閉じますが、さらに`Relese`を呼び出して`workbook`と`workbooks`を解放します。
ここで`COM`オブジェクトを解放しないとエクセルを終了しウィンドウが消えても、エクセルのプロセスが残ってしまうという現象が起こります。
最後にエクセルを終了するために`Quit`を呼びます。ここでも`COM`オブジェクトを確実に解放するために`Release`を呼びます[^3]。
[^3]:実はここで`Release`を省略しても問題ありません。
`_ApplicationPtr`や`WorkbooksPtr`はクラスなので関数やブロック終了時にデストラクターが呼ばれます。デストラクターで`Release`が呼ばれ確実に`COM`オブジェクトは解放されます。
## 例外処理について
`COM`の処理中に何らかのエラーが発生すると、`_com_error`の例外が投げられるので`try cath`で例外を捕捉しエラー処理を行います。
プログラムが異常終了すると、エクセルのプロセスが残ってしまうので、例外は確実に捕捉するようにします。

# COMについて
`COM`については特に解説はしないですが、特有のデータ型 (`BSTR`や`VARIANT`など) に付いては適宜説明します。

# 開発環境および実行環境
- Windows10
- Visual Studio Community 2019
- Excel 2016

以上のような環境で開発しました。
開発環境および実行環境にはエクセルが必要になりますが、それぞれのバージョンは違っていても問題ないようです。