久々のエントリ。
.NetからCOMを参照するときのお話し。単体のオブジェクトを使う分には参照するだけでラッパークラスが作られて楽チンに使えますよね。Marshal.ReleaseComObjectさえ忘れなければ何にも悩むことはないと思います。
で、忘れないようにIDisposableなラッパーを作ったりしてusingでウマーな感じにしたり。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class ComObj<T> : IDisposable
{
private bool disposed = false;
private T target;
public T Target
{
get { return target; }
}
private ComObj(){}
public ComObj(T target)
{
this.target = target;
}
~ComObj()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing) { }
if (target != null && Marshal.IsComObject(target))
{
Marshal.ReleaseComObject(target);
}
target = default(T);
disposed = true;
}
}
}
public static class ComObjUtils
{
public static ComObj<T> Create<T>(T obj)
{
return new ComObj<T>(obj);
}
}
けどもこれってあんましウマーじゃないよなぁと常々思っておりました。vbscriptからいじるにしても結構コード量増えるんで注意しつつ使うのが落とし所かなぁと。そんなこんなで悶々してたところふと思いついたのが「透過プロキシつかってAOP的に解決できんじゃね?」ってのが発端でごそごそ書いてみました。
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections;
namespace COMProxy
{
public class COMProxy<T> : RealProxy
{
private readonly Stack objecctStack;
public Stack ObjecctStack
{
get { return objecctStack; }
}
private readonly T target;
public T Target
{
get { return target; }
}
public COMProxy(T target, Type type,Stack objecctStack)
: base(type)
{
this.target = target;
this.objecctStack = objecctStack;
this.objecctStack.Push(target);
}
public COMProxy(T target, Stack objecctStack)
: base(target.GetType())
{
this.target = target;
this.objecctStack = objecctStack;
this.objecctStack.Push(target);
}
public delegate IMessage BeforeInvokeDelegate(IMessage msg);
public delegate ReturnMessage AfterInvokeDelegate(ReturnMessage msg);
public event BeforeInvokeDelegate BeforeInvoke = delegate(IMessage msg) { return msg; };
public event AfterInvokeDelegate AfterInvoke = delegate(ReturnMessage msg) { return msg; };
public override IMessage Invoke(IMessage msg)
{
msg = BeforeInvoke(msg);
IMethodCallMessage methodCallMessage = msg as IMethodCallMessage;
MethodInfo targetMethod = methodCallMessage.MethodBase as MethodInfo;
object[] args = methodCallMessage.Args;
ReturnMessage returnMessage;
try
{
object invokeResult = targetMethod.Invoke(this.target, args);
//COMオブジェクトを返す場合解放のためにリストに追加する
if (invokeResult != null && Marshal.IsComObject(invokeResult))
{
Type org = invokeResult.GetType();
Type type = (targetMethod.ReturnType == typeof(object)) ? typeof(MarshalByRefObject) : targetMethod.ReturnType;
lock (this.objecctStack.SyncRoot)
{
if (!this.objecctStack.Contains(invokeResult))
{
invokeResult = Utils.Create(invokeResult as MarshalByRefObject,type, this.objecctStack);
}
}
}
returnMessage = new ReturnMessage(invokeResult, args, args.Length, methodCallMessage.LogicalCallContext, methodCallMessage);
}
catch (TargetInvocationException ex)
{
returnMessage = new ReturnMessage(ex.InnerException, methodCallMessage);
}
returnMessage = AfterInvoke(returnMessage);
return returnMessage;
}
public new T GetTransparentProxy()
{
return (T)base.GetTransparentProxy();
}
}
public static class Utils
{
public static T Create<T>(T target,Stack objectStack)
{
return new COMProxy<T>(target, objectStack).GetTransparentProxy();
}
public static T Create<T>(T target,Type type, Stack objectStack)
{
return new COMProxy<T>(target, type, objectStack).GetTransparentProxy();
}
}
}
激しく試行錯誤中でぐだぐだなコードですがやってることはシンプルにメソッド呼出し後にCOMオブジェクト戻す場合は解放に備えてリストに追加してます。てかそれしかしてない^^;
使うほうではこんな感じ
ApplicationClass app = Utils.Create(new ApplicationClass(), COMList);
try
{
Workbooks books = app.Workbooks as Workbooks;
Workbook book = books.Open(
ofd.FileName,
Type.Missing,
true,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing,
Type.Missing);
foreach (Worksheet sheet in book.Worksheets)
{
string cellValue = ((sheet).Cells[2, 2] as Range).Value2.ToString();
MessageBox.Show("Cell value is " + cellValue);
}
}
finally
{
app.Quit();
while (COMList.Count > 0)
{
object comObj = COMList.Pop();
Marshal.ReleaseComObject(comObj);
}
}
何となく方向性はあってそうなのでブラッシュアップして使えるようにしたいなぁ。オブジェクトが戻るときとかいやーんな感じのコードになっちゃってますがCOM使う際の手間の軽減になるかも?と思ってます。なんかぼやぼやしてるとC#4.0でいいじゃんwとか言われかねないのでぐだぐだながら放流してみました^^;
実はもっといい方法あったりします??