概要
エクセルにはCOM
(Component Object Model) という機能を使って、外部のプログラムからセルに書き込んだり、セルの内容を読込んだりすることができる機構が備わっています。
外部プログラムの言語は問いません。COM
をサポートしてればいいので、C++
、VB
、C#
、VBS
、PowerShell
、Python
何でもかまいません。
ここではVC++ (MSVC)
で#import
1を使ってタイプライブラリを読込んで、エクセルを操作します。
タイプライブラリについては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
以上のような環境で開発しました。
開発環境および実行環境にはエクセルが必要になりますが、それぞれのバージョンは違っていても問題ないようです。