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

发布于 2024-08-31 06:30:14 字数 2058 浏览 4 评论 0原文

所有这些问题:

解决 C# 使用 Excel COM 对象后无法正确释放它们的问题。解决此问题主要有两个方向:

  1. 当不再使用 Excel 时,终止 Excel 进程。
  2. 请注意首先将使用的每个 COM 对象显式分配给一个变量,并确保最终在每个 COM 对象上执行 Marshal.ReleaseComObject。

有人说 2 太乏味了,并且总是存在一些不确定性,是否您在代码中的某些地方忘记遵守此规则。 Still 1 对我来说似乎很脏并且容易出错,而且我猜想在受限环境中尝试终止进程可能会引发安全错误。

所以我一直在考虑通过创建另一个模仿 Excel 对象模型的代理对象模型来解决 2(对我来说,实现我实际需要的对象就足够了)。原理如下:

  • 每个 Excel Interop 类都有其包装该类对象的代理。
  • 代理在其终结器中释放 COM 对象。
  • 代理模仿 Interop 类的接口。
  • 任何最初返回 COM 对象的方法都会更改为返回代理。其他方法只是将实现委托给内部 COM 对象。

示例:

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;
    }
}

我向您提出的具体问题是:

  • 谁认为这是一个坏主意,为什么?
  • 谁觉得这是个好主意?如果是这样,为什么还没有人实现/发布这样的模型?这只是由于我的努力,还是我错过了这个想法的致命问题?
  • 在终结器中执行 ReleaseCOMObject 是否不可能/不好/容易出错? (我只看到建议将其放入 Dispose() 而不是放入终结器中 - 为什么?)
  • 如果该方法有意义,有什么改进建议吗?

All of these questions:

struggle with the problem that C# does not release the Excel COM objects properly after using them. There are mainly two directions of working around this issue:

  1. Kill the Excel process when Excel is not used anymore.
  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.

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. 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.

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). The principle would look as follows:

  • Each Excel Interop class has its proxy which wraps an object of that class.
  • The proxy releases the COM object in its finalizer.
  • The proxy mimics the interface of the Interop class.
  • Any methods that originally returned a COM object are changed to return a proxy instead. The other methods simply delegate the implementation to the inner COM object.

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? (I've only seen proposals to put it in a Dispose() rather than in a finalizer - why?)
  • If the approach makes sense, any suggestions to improve it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

转角预定愛 2024-09-07 06:30:14

在析构函数中执行 ReleaseCOMObject 是否不可能/不好/危险? (我只看到过将其放入 Dispose() 而不是析构函数中的建议 - 为什么?)

建议不要将清理代码放入终结器中,因为与 C++ 中的析构函数不同,它不会被确定性地调用。它可能会在对象超出范围后不久被调用。可能需要一个小时。它可能永远不会被调用。一般来说,如果您想处置非托管对象,您应该使用 IDisposable 模式而不是终结器。

您链接到的这个解决方案尝试通过显式调用垃圾收集器并等待终结器完成来解决该问题。一般情况下确实不建议这样做,但对于这种特殊情况,有些人认为这是一个可接受的解决方案,因为很难跟踪创建的所有临时非托管对象。但明确清理是正确的方法。然而考虑到这样做的困难,这种“黑客”可能是可以接受的。请注意,此解决方案可能比您提出的想法更好。

相反,如果您想尝试显式清理,“不要对 COM 对象使用两个点”准则将帮助您记住保留对您创建的每个对象的引用,以便在完成后可以清理它们。

Is it impossible/bad/dangerous to do the ReleaseCOMObject in the destructor? (I've only seen proposals to put it in a Dispose() rather than in a destructor - why?)

It is recommended not to put your clean up code in the finalizer because unlike the destructor in C++ it is not called deterministically. 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.

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.

何时共饮酒 2024-09-07 06:30:14

我们使用 MSDN 杂志中描述的 LifetimeScope 类。正确使用它可以清理对象,并且非常适合我们的 Excel 导出。该代码可以在此处下载,还包含杂志文章:

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

We use the LifetimeScope class that was described in the MSDN magazine. Using it properly cleans up objects and has worked great with our Excel exports. The code can be downloaded here and also contains the magazine article:

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

街角卖回忆 2024-09-07 06:30:14

查看我的项目 MS Office for .NET。通过本机 VB.NET 后期绑定功能解决了引用包装对象和本机对象的问题。

Look at my project MS Office for .NET. There is solved problem with referencich wrapper objects and native objects via native VB.NET late-binding ability.

来日方长 2024-09-07 06:30:14

我会做什么:

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);
}

用例。请注意对 Quit 的调用,这似乎是结束进程所必需的:

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

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:

using (var excel = CleanupComObject(new Excel.Application(), o => o.Quit()))
using (var workbooks = CleanupComObject(excel.Object.Workbooks))
    {
        ...
    }
黎歌 2024-09-07 06:30:14

无论如何,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);
        }
    }

For what it's worth, the Excel Refresh Service on codeplex uses this logic:

    public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
    {
        if (reference == null) return;
        try
        {
            doThis(reference);
        }
        finally
        {
            Marshal.ReleaseComObject(reference);
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文