私です
はじめに
とある仕事でパワポ(のスライドショー)を制御する羽目になり、初めはRuby(Win32OLE)で書いていたのですが、その後に別のプログラムをC#で書く必要があって、パワポ制御プログラムだけRubyなのもなと思い始めたのでC#に移植しました。せっかくなのでその知見を書きます。なお、C#はあまり詳しくないです。
基礎知識
どの言語を使うにしても、Windowsで他のアプリを制御すると言ったらOLEです。
OLEってタグで記事書いてる人いませんね。。。すでに滅びた技術なのかなOLE。
OLEとCOMはどちらが古くからある用語かは私もよく知りません。とりあえず以降ではCOMって方を使います。
C#でのCOMオブジェクト制御
というわけで「C# OLE」でググりました。引っかかったのがこちらの方の記事。
https://fornext1119.hatenablog.com/entry/20120328/p3
一部引用(改行等は修正)
//ワークブックコレクションオブジェクトを生成する。
object excelBooks = excelApp.GetType().InvokeMember(
"Workbooks", BindingFlags.GetProperty, null, excelApp, null
);
//Excelファイルのオープン
object excelBook = excelBooks.GetType().InvokeMember(
"Open", BindingFlags.InvokeMethod, null, excelBooks,
new object[]{
strMacroPath,
System.Type.Missing, System.Type.Missing, System.Type.Missing,
System.Type.Missing, System.Type.Missing, System.Type.Missing,
System.Type.Missing, System.Type.Missing, System.Type.Missing,
System.Type.Missing, System.Type.Missing, System.Type.Missing
}
);
え?まじすか?
というわけで少し前にこちらの記事は見つけていたのですが気乗りしないので移植は放置してました。
問題点は2つあります。
- COMオブジェクトのプロパティやメソッドは
InvokeMember
でアクセスする必要があり上記のようにわかりにくい書き方になる。 - 「指定しない引数」についても
Type.Missing
を渡す必要がある?1
ヘルパークラス作りました
Rubyで書かれたものを移植するのにこんな人間に優しくない表記は嫌なので、せめてもということで以下のヘルパークラスを作りました。
public class OLEHelper {
public static object createObject(string progID)
{
var t = Type.GetTypeFromProgID(progID);
return Activator.CreateInstance(t);
}
public static void freeObject(object o)
{
Marshal.FinalReleaseComObject(o);
}
public static object getProperty(object o, string name)
{
return o.GetType().InvokeMember(name, BindingFlags.GetProperty, null, o, null);
}
public static void setPropery(object o, string name, object value)
{
o.GetType().InvokeMember(name, BindingFlags.SetProperty, null, o, new object[]{value});
}
// paramsは可変長引数
public static object call(object o, string name, params object[] args)
{
return o.GetType().InvokeMember(name, BindingFlags.InvokeMethod, null, o, args);
}
}
このヘルパークラスを使うとなんとかSAN値を下げずにパワポ制御プログラムが書けます。
コメントに書いてあるように結局メソッドを呼び出すときも必要な引数だけで大丈夫なようです。
class Program
{
static void Main(string[] args)
{
var path = Path.GetFullPath(@".\test.pptx");
var powerpoint = OLEHelper.createObject("PowerPoint.Application");
var presentations = OLEHelper.getProperty(powerpoint, "Presentations");
// Openの引数は4つあるが必要な数だけ渡すので大丈夫らしい
var presentation = OLEHelper.call(presentations, "Open", path);
// presentationsを解放しても開いたpresentationに影響はない
OLEHelper.freeObject(presentations);
var slideshowsettings = OLEHelper.getProperty(presentation, "SlideShowSettings");
var slideshowwindow = OLEHelper.call(slideshowsettings, "Run");
OLEHelper.freeObject(slideshowsettings);
var slideshowview = OLEHelper.getProperty(slideshowwindow, "View");
do {
Thread.Sleep(5 * 1000);
OLEHelper.call(slideshowview, "Next");
} while ((int)OLEHelper.getProperty(slideshowview, "State") != 5);
OLEHelper.call(powerpoint, "Quit");
// オブジェクトを全部解放しないとウインドウが閉じられない
OLEHelper.freeObject(slideshowview);
OLEHelper.freeObject(slideshowwindow);
OLEHelper.freeObject(presentation);
OLEHelper.freeObject(powerpoint);
}
}
もう一つ重要な点として、コメントにあるように「全てのCOMオブジェクト」を解放しないとパワポウインドウ、というかプロセスは「パワポ制御プログラム」が終了しても終わりません。
その点で言うと元ネタ記事のプログラムは全オブジェクトを解放してないと思うのでちゃんとExcelが終了するのかちょっと気になります(元ネタのプログラムは動かしてない)
素のobjectじゃなくてIDisposable実装したラッパークラス作ったりすると幸せになりそうな気がしますが、実際のプログラムではオブジェクトの生成と破棄が別メソッドに分かれているのでラッパークラスは作りませんでした。
まとめ
- PowerPointなどOfficeを制御したかったらOLE。
- C#(というか.NET)では
InvokeMember
でプロパティやメソッドが呼び出せる。SAN値が下がるのでヘルパークラスかラッパークラスを作ること推奨。