简体   繁体   English

使用Wrapper对象正确清理excel互操作对象

[英]Using Wrapper objects to Properly clean up excel interop objects

All of these questions: 所有这些问题:

struggle with the problem that C# does not release the Excel COM objects properly after using them. 解决了C#在使用后没有正确释放Excel COM对象的问题。 There are mainly two directions of working around this issue: 解决这个问题主要有两个方向:

  1. Kill the Excel process when Excel is not used anymore. 不再使用Excel时终止Excel进程。
  2. Take care to explicitly assign each COM object used to a variable first and to guarantee that eventually, Marshal.ReleaseComObject is executed on each. 注意首先明确地将用于变量的每个COM对象分配,并保证最终在每个上执行Marshal.ReleaseComObject。

Some have stated that 2 is too tedious and there is always some uncertainty whether you forget to stick to this rule at some places in the code. 有人说过2太繁琐了,你是否忘记在代码中的某些地方忘记遵守这条规则总是存在一些不确定性。 Still 1 seems dirty and error-prone to me, also I guess that in a restricted environment trying to kill a process could raise a security error. 仍然1对我来说似乎很脏并且容易出错,我想在有限的环境中试图杀死一个进程可能会引发安全错误。

So I've been thinking about solving 2 by creating another proxy object model which mimics the Excel object model (for me, it would suffice to implement the objects I actually need). 所以我一直在考虑通过创建另一个模仿Excel对象模型的代理对象模型来解决问题2(对我来说,实现我实际需要的对象就足够了)。 The principle would look as follows: 原则如下:

  • Each Excel Interop class has its proxy which wraps an object of that class. 每个Excel Interop类都有其代理,用于包装该类的对象。
  • The proxy releases the COM object in its finalizer. 代理在其终结器中释放COM对象。
  • The proxy mimics the interface of the Interop class. 代理模仿Interop类的接口。
  • Any methods that originally returned a COM object are changed to return a proxy instead. 最初返回COM对象的任何方法都会更改为返回代理。 The other methods simply delegate the implementation to the inner COM object. 其他方法只是将实现委托给内部COM对象。

Example: 例:

public class Application
{
    private Microsoft.Office.Interop.Excel.Application innerApplication
        = new Microsoft.Office.Interop.Excel.Application innerApplication();

    ~Application()
    {
        Marshal.ReleaseCOMObject(innerApplication);
        innerApplication = null;
    }

    public Workbooks Workbooks
    {
        get { return new Workbooks(innerApplication.Workbooks); }
    }
}

public class Workbooks
{
    private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks;

    Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks)
    {
        this.innerWorkbooks = innerWorkbooks;
    }

    ~Workbooks()
    {
        Marshal.ReleaseCOMObject(innerWorkbooks);
        innerWorkbooks = null;
    }
}

My questions to you are in particular: 我特别向你提问:

  • Who finds this a bad idea and why? 谁发现这是一个坏主意,为什么?
  • Who finds this a gread idea? 谁发现这是一个可怕的想法? If so, why hasn't anybody implemented/published such a model yet? 如果是这样,为什么没有人实施/发布这样的模型呢? Is it only due to the effort, or am I missing a killing problem with that idea? 是仅仅是因为努力,还是我错过了这个想法的杀戮问题?
  • Is it impossible/bad/error-prone to do the ReleaseCOMObject in the finalizer? 在终结器中执行ReleaseCOMObject是不可能/不好/容易出错的吗? (I've only seen proposals to put it in a Dispose() rather than in a finalizer - why?) (我只看到过将它放在Dispose()而不是终结器中的建议 - 为什么?)
  • If the approach makes sense, any suggestions to improve it? 如果方法有意义,有什么建议可以改进吗?

Is it impossible/bad/dangerous to do the ReleaseCOMObject in the destructor? 在析构函数中执行ReleaseCOMObject是不可能/不好/危险的吗? (I've only seen proposals to put it in a Dispose() rather than in a destructor - why?) (我只看到过将它放在Dispose()而不是析构函数中的建议 - 为什么?)

It is recommended not to put your clean up code in the finalizer because unlike the destructor in C++ it is not called deterministically. 建议不要将清理代码放在终结器中,因为与C ++中的析构函数不同,它不是确定性的。 It might be called shortly after the object goes out of scope. 在对象超出范围后不久可能会调用它。 It might take an hour. 可能需要一个小时。 It might never be called. 它可能永远不会被调用。 In general if you want to dispose unmanaged objects you should use the IDisposable pattern and not the finalizer. 通常,如果要处置非托管对象,则应使用IDisposable模式而不是终结器。

This solution that you linked to attempts to work around that problem by explicitly calling the garbage collector and waiting for the finalizers to complete. 您链接的此解决方案通过显式调用垃圾收集器并等待终结器完成来尝试解决该问题。 This is really not recommended in general but for this particular situation some people consider it to be an acceptable solution due to the difficulty of keeping track of all the temporary unmanaged objects that get created. 实际上不建议这样做,但是对于这种特殊情况,有些人认为这是一个可接受的解决方案,因为很难跟踪所有创建的临时非托管对象。 But explicitly cleaning up is the proper way of doing it. 但明确清理是正确的方法。 However given the difficulty of doing so, this "hack" may be acceptable. 然而,考虑到这样做的困难,这种“黑客”可能是可以接受的。 Note that this solution is probably better than the idea you proposed. 请注意,此解决方案可能比您提出的想法更好。

If instead you want to try to explicitly clean up, the "don't use two dots with COM objects" guideline will help you to remember to keep a reference to every object you create so that you can clean them up when you're done. 如果您想尝试明确清理,“不要使用带有COM对象的两个点”指南将帮助您记住保留对您创建的每个对象的引用,以便您可以在完成后清理它们。

We use the LifetimeScope class that was described in the MSDN magazine. 我们使用MSDN杂志中描述的LifetimeScope类。 Using it properly cleans up objects and has worked great with our Excel exports. 使用它可以正确清理对象,并且可以很好地处理Excel导出。 The code can be downloaded here and also contains the magazine article: 代码可以在这里下载,也包含杂志文章:

http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266 http://lifetimescope.codeplex.com/SourceControl/changeset/changes/1266

Look at my project MS Office for .NET . 看看我的项目MS Office for .NET There is solved problem with referencich wrapper objects and native objects via native VB.NET late-binding ability. 通过本机VB.NET后期绑定能力解决了参考包装器对象和本机对象的问题。

For what it's worth, the Excel Refresh Service on codeplex uses this logic: 对于它的价值, codeplex上Excel刷新服务使用以下逻辑:

    public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
    {
        if (reference == null) return;
        try
        {
            doThis(reference);
        }
        finally
        {
            Marshal.ReleaseComObject(reference);
        }
    }

What I'd do: 我要做的是:

class ScopedCleanup<T> : IDisposable where T : class
{
    readonly Action<T> cleanup;

    public ScopedCleanup(T o, Action<T> cleanup)
    {
        this.Object = o;
        this.cleanup = cleanup;
    }

    public T Object { get; private set; }

    #region IDisposable Members

    public void Dispose()
    {
        if (Object != null)
        {
            if(cleanup != null)
                cleanup(Object);
            Object = null;
            GC.SuppressFinalize(this);
        }
    }

    #endregion

    ~ScopedCleanup() { Dispose(); }
}

static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class
{
    return new ScopedCleanup<T>(o, cleanup);
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class
{
    return
        CleanupObject(
            comObject,
            o =>
            {
                if(actionBeforeRelease != null)
                    actionBeforeRelease(o);
                Marshal.ReleaseComObject(o);
            }
        );
}

static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class
{
    return CleanupComObject(comObject, null);
}

Usage case. 用法案例。 Note the call to Quit, which seems to be necessary to make the process end: 注意调用Quit,这似乎是使进程结束所必需的:

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit()))
using (var workbooks = CleanupComObject(excel.Object.Workbooks))
    {
        ...
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM