使用COM互操作处理对象生命周期的最有效方法是什么?
我有一个Windows Workflow应用程序,它使用为COM自动化编写的类。我正在使用COM从类中打开Word和Excel。
我当前正在COM帮助器中实现IDisposable并使用Marshal.ReleaseComObject()。但是,如果我的工作流程失败,则不会调用Dispose()方法,并且Word或者Excel句柄保持打开状态,并且我的应用程序将挂起。
解决此问题的方法非常简单,但我不仅要解决问题,还想学点东西,并深入了解使用COM的正确方法。我正在寻找"最佳"或者最高效,最安全的方式来处理拥有COM句柄的类的生命周期。模式,最佳做法或者示例代码会有所帮助。
解决方案
我看不到我们没有调用Dispose()方法的故障。我使用顺序工作流进行了测试,该工作流仅包含一个引发异常的代码活动,并且我的工作流的Dispose()方法被调用了两次(这是由于使用了标准的WorkflowTerminated事件处理程序)。检查以下代码:
Program.cs
class Program { static void Main(string[] args) { using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) { AutoResetEvent waitHandle = new AutoResetEvent(false); workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); }; workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e) { Console.WriteLine(e.Exception.Message); waitHandle.Set(); }; WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1)); instance.Start(); waitHandle.WaitOne(); } Console.ReadKey(); } }
工作流程1.cs
public sealed partial class Workflow1: SequentialWorkflowActivity { public Workflow1() { InitializeComponent(); this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode); } [DebuggerStepThrough()] private void codeActivity1_ExecuteCode(object sender, EventArgs e) { Console.WriteLine("Throw ApplicationException."); throw new ApplicationException(); } protected override void Dispose(bool disposing) { if (disposing) { // Here you must free your resources // by calling your COM helper Dispose() method Console.WriteLine("Object disposed."); } } }
我想念什么吗?关于一个Activity(以及一个Workflow)对象与生命周期相关的方法,请查看以下文章:Activity"生命周期"方法。如果我们只想了解有关处置的一般文章,请选中此复选框。
基本上,我们不应在工作结束时依靠手动代码在对象上调用Dispose()。我们现在可能有类似这样的内容:
MyComHelper helper = new MyComHelper(); helper.DoStuffWithExcel(); helper.Dispose(); ...
相反,我们需要使用try块来捕获可能触发的任何异常,然后在此时调用dispose。这是规范的方式:
MyComHelper helper = new MyComHelper(); try { helper.DoStuffWithExcel(); } finally() { helper.Dispose(); }
这是如此普遍,以至于Chas是一种特殊的构造,它生成与上面所示相同的确切代码[请参见注释]。这是我们大部分时间应该做的(除非我们具有一些特殊的对象构造语义,这些语义使上面的手动模式更易于使用):
using(MyComHelper helper = new MyComHelper()) { helper.DoStuffWithExcel(); }
编辑:
注意:生成的实际代码比上面的第二个示例稍微复杂一点,因为它还引入了一个新的本地作用域,该局部作用域使helper对象在using块之后不可用。就像第二个代码块被{}包围一样。为了澄清说明,省略了该步骤。