DLR を使った Excel プログラミング


Visual Studio 2010ベータ1のウォークスルーの中に Office プログラマビリティ があります。このウォークスルーの中で、1か所だけ 動的呼び出しになると記述されたところがあります。この個所を、このウォークスルーではNo PIA(埋め込みPIA)というシナリオを確認するために、最終的には、キャストします。なぜキャストするかといえば、埋め込みPIAでは必要な型情報のみを取り込むからだと説明されています。この内容は、さておきNo PIAにしないコードの抜粋を以下に引用します。

public class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}

using Excel = Microsoft.Office.Interop.Excel;
class Program
{
static void Main(string[] args)
{
var checkAccounts = new List<Account> {
new Account { ID = 345, Balance = 541.27 },
new Account { ID = 123, Balance = -127.44 } };

DisplayInExcel(checkAccounts, (account, cell) =>
{ cell.Value2 = account.ID;
cell.get_Offset(0, 1).Value2 = account.Balance;
if (account.Balance < 0)
{ cell.Interior.Color = 255;
cell.get_Offset(0, 1).Interior.Color = 255;
}
});
}

public static void DisplayInExcel(
IEnumerable<Account> accounts,
Action<Account, Excel.Rage> DisplayFunc)
{
var xl = new Excel.Application();
xl.Workbooks.Add();
xl.Visible = true;
xl.get_Range("A1").Value2 = "ID";
xl.get_Range("B1").Value2 = "Balance";
xl.get_Range("A2").Select();
foreach (var ac in accounts)
{ DisplayFunc(ac, xl.ActiveCell);
xl.ActiveCell.get_Offset(1, 0).Select();
}
xl.get_Range("A1:B3").Copy();

xl.Columns[1].AutoFit();
xl.Columns[2].AutoFit();

}
}


xl.Columns[1].AutoFit();」が実行時に解決されて、AutoFitメソッドが呼ばれます。この実行時に解決するというのは、xl.Columns[1]が実行時にRangeオブジェクトを返すために、AutoFitメソッドを呼び出せるようになるという意味です。このウォークスルーでは埋め込みPIAを確認するためですが、最終的に「((Excel.Range) xl.Columns[1]).AutoFit();」とキャストを追加するようになります。

 しかし、.NET Framework 4.0ベータ1には動的呼び出しをサポートするために DLRが含まれています。DLRを使うようにすることで、埋め込みPIAどころか、型情報自体を実行時に解決することができます。そのように改造した Programクラスを以下に示します。

//using Excel = Microsoft.Office.Interop.Excel;
class Program
{
static void Main(string[] args)
{
var checkAccounts = new List<Account> {
new Account { ID = 345, Balance = 541.27 },
new Account { ID = 123, Balance = -127.44 } };

DisplayInExcel(checkAccounts, (account, cell) =>
{ cell.Value2 = account.ID;
cell.Offset(0, 1).Value2 = account.Balance;
if (account.Balance < 0)
{ cell.Interior.Color = 255;
cell.Offset(0, 1).Interior.Color = 255;
}
});
}

public static void DisplayInExcel(
IEnumerable<Account> accounts,
Action<Account, dynamic> DisplayFunc)
{
dynamic xl = GetComInstance("Excel.Application");
xl.Workbooks.Add();
xl.Visible = true;
xl.Range("A1").Value2 = "ID";
xl.Range("B1").Value2 = "Balance";
xl.Range("A2").Select();
foreach (var ac in accounts)
{ DisplayFunc(ac, xl.ActiveCell);
xl.ActiveCell.Offset(1, 0).Select();
}
xl.Range("A1:B3").Copy();

xl.Columns[1].AutoFit();
xl.Columns[2].AutoFit();
}
// COMインスタンス作成用のヘルパーメソッド
public static dynamic GetComInstance(string progID)
{ Type comType = Type.GetTypeFromProgID(progID);
return System.Activator.CreateInstance(comType);
}

}



プロジェクトからPIAアセンブリへの参照を削除して、ビルドすれば DLR を活用した動的呼び出しが完成します。C#では、PIAを使っている時にGet_RangeやGet_Offsetというメソッドだったことにも注意してください。VBと同じように「Get_」プレフィックスのつかないメソッドで呼び出せるようになります。これは、dynamicキーワードによってC# Binderが DLR のCOM呼び出し(VB6のレイトバインディングと同等)を呼び出すからです。


VB のウォークスルーのコードも、型名を Object に変更すれば、同じように動的呼び出しを実現することができます。VBの場合は、VBコンパイラがVB Binderである NewLateBindingクラスを利用するコードを出力することで実現しています。


PIAがあれば実行時の型変換の規則がありますので実行速度的には良いでしょうが、DLRを使った動的呼び出しも私には良さそうに思えます。皆さんは、どちらがお好きでしょうか?


追記:VBではNewLateBindingクラスが活躍しますが、DLRに対する呼び出しなどは IDOBinderクラスとIDOUtilsクラスを使って処理しているようです。C#では、CTPの時と違いMicrosoft.CSharp.dllアセンブリにRuntimeBinderネームスペースが存在しています。

Comments (8)

  1. hidori より:

    こんにちは、ご無沙汰しています。

    dynamic キーワード(DynamicObject) はすごく便利だと思うんですが、COM オブジェクトのリリースまでは面倒見てくれないですよね?

    DynamicObject が、自分自身がカプセルしているブツが COM オブジェクトだと理解しているとしても、それを解放するべき適切なタイミングを知ることができないような気がしているんですが…実際のところはどうなんでしょう?

  2. shozoa より:

    ご指摘の通りです。

    GCで解放してやるしかないと思います。現在のベータでどうかは試していませんが、WORDとEXCELでも挙動が異なります(厳密には、COMサーバーの作りに依存しているように私には思えます)。この意味では、dynamicキーワード(実態は属性)を持ったobjectが、参照を失うことでGCが回収するでしょう。

  3. hidori より:

    >GCで解放してやるしかないと思います

    GC 任せに出来ない場面も、現実世界には多々あります。

    なので、dynamic キーワードを使っていても、明示的に COM の参照を解除する方法はどうしても必要です。

    dynamic キーワード関連のヘルプトピックをざっと見てみましたが、記法や利点について述べたものばかりで、「COM 参照の解放はプログラマの責任」であることについて言及されたものは見つけられませんでした。

    これは技術情報として明確にどこかで説明されるべきだ事項だと思います。

    一応、VS2010Beta1 では、COM サーバをカプセルする dynamic な変数に Marshal.ReleaseComObject() すると IUnknown.Release() が呼び出されているようですが、これが保障された動作であるのかどうかも分りません。

  4. shozoa より:

    >明確にどこかで説明されるべきだ事項だと思います

    それは、その通りだと思います。

    ソースコードの ComRuntimeHelper.csのIUnknownReleaseDelegateのサマリーには、間接的にvTableの関数ポインタを呼び出すコードを出力している。このアプローチは、Marshal.Release向けの900ものコードよりも300の命令になるし、JITがP/Invokeスタブのインライン化とP/Invokeのターゲットを直接呼び出すようになると書かれています。

    これよりもIUnknownReleaseDelegateというデリゲートのネーミングからも推測できるように、IUnknownをリリースする仕組みを組み込んでいるので、説明が書かれていないのかも知れません。

    >dynamic な変数に Marshal.ReleaseComObject()

    CLRでは、COMオブジェクトのインスタンスはRCWのスタブで必ず包まれます。ですから、保障された動きであると考えています。もしろdynamicという属性は、振る舞いをDLRのバインダーにお任せするというものであって、メソッドやメンバーの呼び出し方法が直接的にマネージ・ポインタを使うのではなく、名前でMehodInfo(マネージポインタ)を取得してからInvokeするという仕組みに変わるだけだと理解しています。

  5. hidori より:

    IUnknownReleaseDelegate は Microsoft.Scripting.dll に含まれた定義のようですね。

    Microsoft.Scripting.dll は、.NET Framework 4.0 Beta1 には含まれておらず、別途 CodePlex で配布されるようです。

    なので、IUnknownReleaseDelegate は、Microsoft.Scripting.dll がスクリプト言語実装に対して提供するサービス内で使用されるモノのような気がします。

    なので、.NET Framework 4.0 環境で dynamic キーワードを使用してプログラムを書くだけでは、IUnknownReleaseDelegate の恩恵は受けられないような…

    >>dynamic な変数に Marshal.ReleaseComObject()

    >

    >CLRでは、COMオブジェクトのインスタンスはRCWのスタブで必ず包まれます。ですから、保障された動きであると考えています。

    なるほど。そうですね。

    遅延バインドによるメンバ呼び出しを陰でサポートしてくれるだけで、扱っているオブジェクトそのものは今まで通り 、ってことなので、その動作もこれまで通りということになりますね。

    ちょっと安心しましたw

  6. shozoa より:

    hidoriさん、有難うございます。

    >IUnknownReleaseDelegate は Microsoft.Scripting.dll に含まれた定義のようですね

    DLR 0.91のソースコードを見ると Microsoft.Dynamicに含まれています。.NET Framework 4.0Beta1よりも開発が進んでいますので、最終的にはSystem.Dynamicネームスペースに入るのではないかと考えています。

    まだ開発途中なので何とも言えませんが、codeplexのDLRが開発が進んだものであることは間違いありませんので、Beta2で入っていれば、その恩恵がdynamicキーワードでも受けられるのじゃないでしょうか。

    #複数バージョンのソース(IronPython、IronRuby、DLR)を見ていると、みるバージョンによってかなり異なっていくので、なるべく最新を見るようにしています。

  7. DLRを使ったExcelプログラミング というエントリーで、興味深いご指摘をいただきました。それは、 COMオブジェクトのリリースを誰が面倒を見てくれるのか というものです。この問題を考える上で意識しないといけないのが、オブジェクトのライフサイクルの管理という側面です。具体的には、以下のようなものです。

Skip to main content