VC++からExcel,Wordを操作したかった

以下、昔の記事です。

僕は開発屋という仕事をしている。今のところVBとVCの狭間をあっちへ行ったり、こっちへ行ったりという風に仕事をしていた。(最近でこそVCの方に落ち着いてきたが)
VBとVCの間を行き来してて思ったのは、当然といえば当然なのだが、VBの適用範囲の狭さである。
例えば、スレッドがない、名前付けパイプがない、メールスロットがない、などなど。
VBでもやろうと思えばできないことはないのだが、結局API関数をDECLAREしなきゃいけない。つまり、Win32SDKの力を借りなければ何もできないのである。
だが、VBにはそれを補って余りある使い勝手のよさがある。最近ではDirectXへの対応もできていると聞いた。
今後もVBはすたれることはないだろう。
ただ、VBでできることはVCで全て出来るという考えが僕にはあった。
・・・しかし、僕の頭を長年にわたって支配し続けるものが一つあった。
Excel,Wordの制御である。

MicrosoftOfficeを代表する2大ソフトExcel,Wordの名前を聞いたことのないサラリーマンはいないと思う。
一太郎とWordがそのシェアを争っていたのはいつのことやら、他Officeファミリーとの親和性がWordが一太郎との距離を開け始め、いつの間にやら、コンピュータースクールで教材として取り上げられるNo1,No2ソフトとなってしまった。新規に事務系バイトを始めるにしても、Excel,Wordができると言うだけで人より抜きんでることができる。
それぐらいExcel,Wordは普及している。

VBとOfficeの親和性の高さはもはや自明の理でもある。
例えば、OfficeにはVBAというVBに準じた言語が存在する。このVBAVBとほぼ同一のものであると言っても、過言ではない。
VBA上のコードをVBにそのまま移植しても動作可能であるし、逆もまた可である。

VBからExcelを操作する場合には以下のようなコードを使用する。

Set obj as New Excel.Application

または、

Dim obj as Excel.Application
Set obj = CreateObject("Excel.Application.8") ' Excel97の場合

または、

Dim obj as Excel.Workbook
Set obj = GetObject("c:tempsampl.xls")

っというところだろうか。
これらのことはVBのヘルプ上に記述されている。

しかし、VC++においては、Excelの操作方法はあまり具体的には書かれていない。

ちなみにMicrosoftのサポート技術情報には以下のように書かれている。

[MSVC] MFCExcel の OLE オートメーション を使用するには

AppWizard を使用し OLE オートメーション を ON にしてアプリケーションを新規作成します。
ClassWizard で Excel の OLB ファイルを使って COleDispatchDriver から派生させた Application クラスを自動作成させます。

コーディング例


void CEeeeDlg::OnButton1()
{
Application MyExcel; // ClassWizard と Excel の OLB ファイル
// で作成したクラス
COleVariant variant; // VARIANT 型
MyExcel.CreateDispatch("Excel.Application.5"); // 接続
variant.ChangeType(VT_NULL);
MyExcel.Help(variant,variant); // Excel の機能の Help
AfxMessageBox("一時停止");
MyExcel.Quit(); // Excel の機能の Quit
MyExcel.ReleaseDispatch(); // 切断
}



悪いが、これでは実際にCellにデータを設定するにはどうすればいいか、などということは全く分からない。
さらに付け加えると、このままではExcel97を操作することはできない。カンの鋭い方ならお気づきだと思うが、"Excel.Application.5"はExcel95用のクラス名称である。

話を先に進めよう。結論から言うと、以下のような記述でExcelの操作が可能になる。
Excelに"HELLO!!"と表示するコードだ。
少々長いのがたまにキズだが、我慢してほしい。

1.AppWizard を使用し OLE オートメーション を ON にしてアプリケーションを新規作成する。
2.ClassWizard で Excel の OLB ファイルを使って COleDispatchDriver から派生させた クラスApplication,Workbook,Workbookを自動作成させる。
ただし、正しいOLBファイルを選択する必要がある。Excel97の場合はexcel8.olbのはず。

コーディング例


_Application Xlap;
Workbooks Xlbks;
_Workbook Xlbk;
Sheets Xlsts;
_Worksheet Xlst;
Range Xlcells;
Range Xlcell;

CLSID clsid;
LPDISPATCH pDisp;
LPUNKNOWN pUnk;
COleVariant varnull;

// Applicationオブジェクトの取得
::CLSIDFromProgID(L"Excel.Application.8", &clsid);
if(::GetActiveObject(clsid, NULL, &pUnk) == S_OK)
{
// 既にExcelが起動されている場合
pUnk>QueryInterface(IID_IDispatch, (void**) &pDisp );
Xlap.AttachDispatch(pDisp);
}
else
{
Xlap.CreateDispatch("Excel.Application.8");
}

// Workbooksオブジェクトの取得
Xlbks.AttachDispatch( Xlap.GetWorkbooks());

// Workbookオブジェクトの取得
// Workbookは.xlsファイルに相当する。
varnull.ChangeType(VT_NULL);
Xlbk.AttachDispatch(Xlbks.Add(varnull));

// Sheetsオブジェクトの設定
Xlsts.AttachDispatch(Xlbk.GetWorksheets());

// Worksheetオブジェクトの取得
// この場合は一つ目のシート
Xlst.AttachDispatch(Xlsts.GetItem(COleVariant*1;

// Cel(Row=1, Cel=2)を取得
Xlcel.AttachDispatch(Xlcells.GetItem2(COleVariant*2;
となる。

とりあえず、上記のような方法でExcelの操作が可能になる。(ただしExcel97)。
あとは応用だ。Wordもほぼ似たような感じだと思って間違いない。
多分、インターネットでもそれほど扱われていない内容だと思う。掲載されていない内容だからこそ散々苦労した。
OLEの本で調べる?値段が高いよ、あの手の本は。
もし、「ここにも掲載されていたよ」という情報があるならば、ご一報願いたい。
少なくとも、僕は見たことがない。

*1:long) 1, VT_I4)));

// Worksheetオブジェクト全体をRangeとして取得
Xlcells.AttachDispatch(Xlst.GetCells(

*2:long) 1, VT_I4), COleVariant((long) 2, VT_I4)));

// Celに値を設定
Xlcel.SetValue(COleVariant(COleVariant("HELLO!!")));

// 終了処理
Xlap.Quit();
Xlcel.ReleaseDispatch();
Xlcells.ReleaseDispatch();
Xlst.ReleaseDispatch();
Xlsts.ReleaseDispatch();
Xlbk.ReleaseDispatch();
Xlbks.RleaseDispatch();
Xlap.ReleaseDispatch();



また、Xlcells.GetItem2という定義されていない部分が出てきたと思う。
これは、クラスRangeに、Range::GetItemを少々いじった以下のような関数を加える必要がある。


LPDISPATCH Range::GetItem2(const VARIANT& RowIndex, const VARIANT& ColumnIndex)
{
LPDISPATCH result;
static BYTE parms[] = VTS_VARIANT VTS_VARIANT;
InvokeHelper(0xaa, DISPATCH_PROPERTYGET, VT_DISPATCH, (void*)&result, parms,
&RowIndex, &ColumnIndex);
return result;
}



Ragne::GetItemと比較してもらえれば分かると思うが、VT_VARIANTの部分がVT_DISPATCHに変わっている。

VBにおいては、
Dim app as New Excel.Application
Dim obj as Object
Set obj = app.GetWorkbooks
みたいな書き方があるが、VC++(というかMFC)においては、
ap as _Application ;
bks as Workbooks ;
ap.CreateDispatch("Excel.Application.8");
bks.AttachDispatch(ap.GetWorkbooks(