概要
エクセルにはCOM(Component Object Model) という機能を使って、外部のプログラムからセルに書き込んだり、セルの内容を読込んだりすることができる機構が備わっています。
外部プログラムの言語は問いません。COMをサポートしてればいいので、C++、VB、C#、VBS、PowerShell、Python 何でもかまいません。
ここではVC++ (MSVC) で#import1を使ってタイプライブラリを読込んで、エクセルを操作します。
タイプライブラリについてはMicrosoft DocsのCOM、DCOM、およびタイプ ライブラリを参照して下さい。
なお、他にもMFCを使った方法、MFCも#importも使わない方法かありますが、ここでは触れません。
エクセルを起動
まずエクセルを起動するだけのプログラムです。
#importでタイプライブラリを読込む時に、libidを使用しているので、エクセルのバージョンには依存しないはずです。
#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を使用しない場合は次のように記述します。
#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を使った方が簡単で確実です。
エクセルのセルにデータを書き込む
さすがにこれだけではさみしいので (ワークブックを開くだけでしたら、ShellExecuteのほうが簡単) 、セルにデータを書き込んでファイルに保存してみます。
保存したファイル (ワークブック) はデフォルトではおそらくドキュメント フォルダーにあります。(カレント フォルダーではないです)
#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関数の開始前) に確実にコンストラクターで初期化し、終了時にデストラクターでクローズ処理を行うようにします。
//Excelの起動
Excel::_ApplicationPtr excelApp;
excelApp.CreateInstance(L"Excel.Application");
_ApplicationPtrがアプリケーションオブジェクトです。VBAのApplicationに相当します。
VBAでは普通Applicationを明示することは無いですが、VC++では省略できないです。
CreateInstanceでエクセルを起動しますが、注意して頂きたいのはエクセルは実行中とは別のプロセスで稼働すると言うことです。
別のプロセスなので終了処理を確実に行わないと、エクセルのプロセスが残ってしまうと言うことです。
とくに難しいことではないのですが、手順を確実に守る必要があります。
//新規でのWorkBookを追加
Excel::WorkbooksPtr workbooks = excelApp->Workbooks;
Excel::_WorkbookPtr workbook = workbooks->Add();
//アクティブ・シートを取得
Excel::_WorksheetPtr worksheet = workbook->ActiveSheet;
WorkbooksPtrがワークブックコレクションで、_WorkbookPtrがワークブックオブジェクトです。
VBAではアクティブシートに対する操作に関してはオブジェクトを省略できますが、VC++では省略できなので、ActiveSheetプロパティでアクティブシートオブジェクト (_WorksheetPtr) を取得して操作対象にします。
//セルに文字列を書き込む
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。
例外処理について
COMの処理中に何らかのエラーが発生すると、_com_errorの例外が投げられるのでtry cathで例外を捕捉しエラー処理を行います。
プログラムが異常終了すると、エクセルのプロセスが残ってしまうので、例外は確実に捕捉するようにします。
COMについて
COMについては特に解説はしないですが、特有のデータ型 (BSTRやVARIANTなど) に付いては適宜説明します。
開発環境および実行環境
- Windows10
- Visual Studio Community 2019
- Excel 2016
以上のような環境で開発しました。
開発環境および実行環境にはエクセルが必要になりますが、それぞれのバージョンは違っていても問題ないようです。