Microsoft Office®に搭載されたマクロ機能。正式にはVisual Basic for Applications(略して「VBA」)と呼ばれております。
このVBAは、外部アプリケーションから参照・操作できるようになっておりまして、いわゆるComponent Object Model(略して「COM」)として提供されており、その歴史は古く、僕の知る限り少なくともMicrosoft Office®97ですでに参照・操作が可能となっておりました。
(本稿とは全く関係ありませんが、PC内でのアプリケーション連携をCOMという一方で、PCを跨いだアプリケーション連携をDCOMといい、このDCOMがしばしば脆弱性を狙われる標的になっていたりします。)
時代は変わって世の中がCOMから.NETへとシフトしていくわけですが、大多数の基盤がCOMから.NETへと取って代わっていく傍らで、VBAは相も変わらずCOMのまま提供され続けており、.NETアプリケーションから参照・操作するにはCOMオブジェクト特有の基本作法が必要になります。
本稿は、僕の個人的な備忘録(というより、いちいち過去に書いたソースコードを開いてコピペするのが面倒なので、どこでもいいから書いておきたかった)という位置づけでありますので、基本要件がだいぶ端折られておりますことを予めご承知おきください。
いきなりですが、C#で書くなら.NET Framework®4.0以降を使いましょう
C#で、.NET Framework®4.0未満を使用してCOMオブジェクトを操作するようなアプリ開発を求められたなら、とにかくダッシュで逃げましょう。
基本的にC#は変数の型を事前に宣言して使う言語ですから、そのままCOMと付き合おうとするとプログラムの基本動作に関係ないコードを大量に書くことに時間を費やされる悲劇に見舞われます。
VBAでは省略できる引数をすべて書かなきゃいけなかったり、そのためにVBA用のNULLオブジェクトを書いたり、などなど、こんなことをするためにプログラマになったのではありません!と絶叫することになるはずだからです。
.NET Framework®4.0からdynamic型という変数型が登場しました。
このdynamic型は、コンパイル時には中身が評価されないことから、実行時に初めてバインドされるCOMオブジェクトへの参照を格納するのに最適な型です。
というより、ほぼこのために用意された変数型です。
また、VBAでは、名前を指定して引数を渡す習慣があります。
Documents.Open FileName:="C:\temp\test.docx", ReadOnly:=True
こんな具合に。
C#にも.NET Framework
®4.0以降で、Named Parameters(名前付き引数)が登場し、名前を指定して引数を渡す書き方が可能になりました。
WordApplicationObject.Documents.Open(FileName: "C:\\temp\\test.doc", ReadOnly: true);
こんな具合ですね。
dynamic型と名前付き引数の登場によって、プログラムの基本動作に関係しない引数を省略するVBAでは当たり前の書き方が、C#でも可能になりました。
それ故に、繰り返しになりますが、C#で書くなら.NET Framework®4.0以降を使うことを強く推奨します。
また本項では、.NET Framework®4.0未満による基本作法は扱いませんし、今後も僕が.NET Framework®4.0未満による基本作法を書くことはないと思います。
早速、COMオブジェクトを参照してみましょう
COMプログラミングでは、オブジェクトを参照する方法として、次の2とおりの方法がありました。
正確に統計を取ったわけでもないので参考程度の体感情報に過ぎませんが、COMプログラミング全盛の時代でも、CreateObject関数を使用して参照する書き方をする人が圧倒的に多かったような気がします。
最終的にコンパイルする環境とは別の環境でコーディングする機会が多かったせいでしょうか。にわかプログラマの僕は正確に把握しておりませんが、見よう見まねの僕は、製品または準製品として「納品」というプロセスが必要な場合には、CreateObjectしておりました。
.NETからCOMオブジェクトを参照する場合は、CreateObject関数と同じような方法で参照することになります。
そしてたぶん、下記の静的メソッドをコピーして使用していただくのが最も手っ取り早い方法になるかと思います。
private static dynamic CreateObject(string ProgID, string ServerName)
{
Type t;
if (ServerName == null || ServerName.Length == 0) { t = Type.GetTypeFromProgID(ProgID); }
else { t = Type.GetTypeFromProgID(ProgID, ServerName, true); }
return Activator.CreateInstance(t);
}
この静的メソッドを呼び出す構文は、次のような具合です。
var WdApp = CreateObject("Word.Application", null);
COMオブジェクトはちゃんと後片付けをしないと、予期せぬ動作が起こりデバッグでハマります
さてさて、COMオブジェクトさえ参照できてしまえばこっちのもの!とでも言わんばかりに、ここまで知るとガシガシとコーディングしたくなるのが人の常(僕だけでしょうか、、、)ですが、家に帰るまでが遠足であるのと同じように、COMオブジェクトを解放するまでが.NETプログラミングです。
たとえば、フォームアプリケーションのボタンに次のようなコードを書いて、Wordを起動した直後に終了してみることにします。
var WdApp = CreateObject("Word.Application", null);
WdApp.Quit(false);
フォームアプリケーションを実行してボタンを押したら、タスクマネージャを開いてみましょう。
起動した直後に終了したはずの「WINWORD.EXE」というプロセスが残っていませんか?
この現象は、COMオブジェクト(この例ではApplicationオブジェクトやDocumentオブジェクトなど)への参照を変数に割り当てたまま、ちゃんと後片付けしていないときにも発生します。
つまり、閉じたはずの一時的な文書が開いているために、同じ名前で一時的な文書を保存しようとしても、ファイルIOレベルでエラーになるという事態に見舞われます。
COMオブジェクトへの参照を割り当てた変数の数だけ、次の構文を実行して解放します。
var WdApp = CreateObject("Word.Application", null);
・
・
・
WdApp.Quit(false);
・
・
・
while (System.Runtime.InteropServices.Marshal.ReleaseComObject((object)WdApp) > 0) { }
僕の方で実績のある方法は上記構文になるのですが、whileループを代わりに実行してくれるFinalReleaseComObjectメソッドなるものも用意されているようです。
いずれ折をみて、実験してみようと思います。
COMオブジェクトへの参照を変数に割り当てるなら、IDisposableなクラスを使用すると安心です
usingで括っておくだけで、記述した必要な後片付けをやってくれるIDisposableなクラス。
この便利で優しいIDisposableなクラスは、COMオブジェクトの後片付けにおいても有効です。
次のようにクラスを書くことで、そのクラスはIDisposableなクラスになり、usingで括って使用することが可能になります。
public class IDisposableNaClass : IDisposable
{
bool _disposed = false;
public IDisposableNaClass ()
{
・
・
・
}
・
・
・
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool Disposing)
{
if (!this._disposed)
{
if (Disposing)
{
// 解放するマネージドコードをここに書く。
}
// 解放するアンマネージドコードをここに書く。
this._disposed = true;
}
}
}
呼び出すときはこう書きます。
using (var __idisposableNaClass = new IDisposableNaClass())
{
・
・
・
}