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
Anonymous
June 01, 2009
PingBack from http://asp-net-hosting.simplynetdev.com/dlr-%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%9f-excel-%e3%83%97%e3%83%ad%e3%82%b0%e3%83%a9%e3%83%9f%e3%83%b3%e3%82%b0/Anonymous
June 02, 2009
こんにちは、ご無沙汰しています。 dynamic キーワード(DynamicObject) はすごく便利だと思うんですが、COM オブジェクトのリリースまでは面倒見てくれないですよね? DynamicObject が、自分自身がカプセルしているブツが COM オブジェクトだと理解しているとしても、それを解放するべき適切なタイミングを知ることができないような気がしているんですが…実際のところはどうなんでしょう?Anonymous
June 02, 2009
ご指摘の通りです。 GCで解放してやるしかないと思います。現在のベータでどうかは試していませんが、WORDとEXCELでも挙動が異なります(厳密には、COMサーバーの作りに依存しているように私には思えます)。この意味では、dynamicキーワード(実態は属性)を持ったobjectが、参照を失うことでGCが回収するでしょう。Anonymous
June 11, 2009
The comment has been removedAnonymous
June 11, 2009
>明確にどこかで説明されるべきだ事項だと思います それは、その通りだと思います。 ソースコードの 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するという仕組みに変わるだけだと理解しています。Anonymous
June 13, 2009
The comment has been removedAnonymous
June 13, 2009
hidoriさん、有難うございます。 >IUnknownReleaseDelegate は Microsoft.Scripting.dll に含まれた定義のようですね DLR 0.91のソースコードを見ると Microsoft.Dynamicに含まれています。.NET Framework 4.0Beta1よりも開発が進んでいますので、最終的にはSystem.Dynamicネームスペースに入るのではないかと考えています。 まだ開発途中なので何とも言えませんが、codeplexのDLRが開発が進んだものであることは間違いありませんので、Beta2で入っていれば、その恩恵がdynamicキーワードでも受けられるのじゃないでしょうか。 #複数バージョンのソース(IronPython、IronRuby、DLR)を見ていると、みるバージョンによってかなり異なっていくので、なるべく最新を見るようにしています。Anonymous
June 14, 2009
DLRを使ったExcelプログラミング というエントリーで、興味深いご指摘をいただきました。それは、 COMオブジェクトのリリースを誰が面倒を見てくれるのか というものです。この問題を考える上で意識しないといけないのが、オブジェクトのライフサイクルの管理という側面です。具体的には、以下のようなものです。