*每个* Excel 互操作对象都需要使用 Marshal.ReleaseComObject 来释放吗?

发布于 2024-09-02 22:28:45 字数 3932 浏览 5 评论 0原文

编辑

另请参阅如何正确清理 Excel 互操作对象?。我最近遇到了这个问题,它为如何正确处理 COM 对象的问题提供了很多见解。一定要检查第一个(标记的)答案之外的内容,因为其他答案超出了简单的“不要使用两个点”和“为每个 com 对象使用 ReleaseComObject”建议。

我首先重新审视了这个问题,因为我意识到,尽管我非常彻底地注册和处置了所有 COM 对象,但我的 Excel 实例仍然没有得到正确处置。事实证明,有一些完全不明显的方法可以创建 COM 对象(即,即使您从不使用两个点,您也可能会错过 COM 对象)。另外,即使你做得很彻底,如果你的项目增长超过一定规模,丢失 COM 对象的几率也接近 100%。当这种情况发生时,你可能很难找到你错过的那个。上面链接的问题的答案提供了一些其他技术来确保 Excel 实例确实关闭。同时,我对我的 ComObjectManager (如下)进行了一个小(但重要)更新,以反映我从上面链接的问题中学到的知识。

原始问题

我见过几个将 Marshal.ReleaseComObject() 与 Excel Interop 对象(即来自命名空间 Microsoft.Office.Interop.Excel 的对象)一起使用的示例,但是我见过它在不同程度上被使用过。

我想知道我是否可以摆脱这样的事情:

var application = new ApplicationClass();
try
{
    // do work with application, workbooks, worksheets, cells, etc.
}
finally
{
    Marashal.ReleaseComObject(application)
}

或者如果我需要释放创建的每个对象,就像在这个方法中一样:

public void CreateExcelWorkbookWithSingleSheet()
{
    var application = new ApplicationClass();
    var workbook = application.Workbooks.Add(_missing);
    var worksheets = workbook.Worksheets;
    for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
    {
        var worksheet = (WorksheetClass)worksheets[worksheetIndex];
        worksheet.Delete();
        Marshal.ReleaseComObject(worksheet);
    }
    workbook.SaveAs(
        WorkbookPath, _missing, _missing, _missing, _missing, _missing,
        XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
    workbook.Close(true, _missing, _missing);
    application.Quit();
    Marshal.ReleaseComObject(worksheets);
    Marshal.ReleaseComObject(workbook);
    Marshal.ReleaseComObject(application);
}

促使我问这个问题的是,作为 LINQ 爱好者,我真的想要做这样的事情:

var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name);

...但我担心如果我不释放每个工作表(ws)对象,我最终会出现内存泄漏或幽灵进程。

任何对此的见解将不胜感激。

更新

根据到目前为止的答案,听起来我确实需要释放我创建的每个 com 对象。我借此机会构建了一个 ComObjectManager 类,以使其更容易处理这个令人头疼的问题。您必须记住每次实例化新的 com 对象时都使用 Get() 方法,但如果您这样做,它将为您处理其他所有事情。如果您发现任何问题,请告诉我(如果可以的话,请编辑并发表评论)。这是代码:

public class ComObjectManager : IDisposable
{
    private Stack<object> _comObjects = new Stack<object>();

    public TComObject Get<TComObject>(Func<TComObject> getter)
    {
        var comObject = getter();
        _comObjects.Push(comObject);
        return comObject;
    }

    public void Dispose()
    {
        // these two lines of code will dispose of any unreferenced COM objects
        GC.Collect();
        GC.WaitForPendingFinalizers();

        while (_comObjects.Count > 0)
            Marshal.ReleaseComObject(_comObjects.Pop());
    }
}

这是一个使用示例:

public void CreateExcelWorkbookWithSingleSheet()
{
    using (var com = new ComObjectManager())
    {
        var application = com.Get<ApplicationClass>(() => new ApplicationClass());
        var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing));
        var worksheets = com.Get<Sheets>(() => workbook.Worksheets);
        for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
        {
            var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]);
            worksheet.Delete();
        }
        workbook.SaveAs(
            WorkbookPath, _missing, _missing, _missing, _missing, _missing,
            XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
        workbook.Close(true, _missing, _missing);
        application.Quit();
    }
}

Edit

Please see also How do I properly clean up Excel interop objects?. I recently came across this question, and it provided a lot of insight into the problem of how to properly dispose of COM objects. Definitely check beyond the first (marked) answer, because the other answers go beyond the simple "don't use two dots" and "use ReleaseComObject for every com object" advice.

I revisited this question in the first place because I realized that, despite being very thorough about registering and disposing all my COM objects, my Excel instances still weren't being properly disposed. It turns out, there are ways COM objects can be created that are completely non-obvious (i.e., you can miss COM objects even if you never use two dots). In addition, even if you are thorough, if your project grows beyond a certain size, the chance of missing a COM object approaches 100%. And it can be very hard to find the one you missed when that happens. The answers to the question linked above provide some other techniques for making sure the Excel instance definitely gets closed. Meanwhile, I've made a small (but significant) update to my ComObjectManager (below) to reflect what I learned from the question linked above.

Original Question

I've seen several examples where Marshal.ReleaseComObject() is used with Excel Interop objects (i.e., objects from namespace Microsoft.Office.Interop.Excel), but I've seen it used to various degrees.

I'm wondering if I can get away with something like this:

var application = new ApplicationClass();
try
{
    // do work with application, workbooks, worksheets, cells, etc.
}
finally
{
    Marashal.ReleaseComObject(application)
}

Or if I need to release every single object created, as in this method:

public void CreateExcelWorkbookWithSingleSheet()
{
    var application = new ApplicationClass();
    var workbook = application.Workbooks.Add(_missing);
    var worksheets = workbook.Worksheets;
    for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
    {
        var worksheet = (WorksheetClass)worksheets[worksheetIndex];
        worksheet.Delete();
        Marshal.ReleaseComObject(worksheet);
    }
    workbook.SaveAs(
        WorkbookPath, _missing, _missing, _missing, _missing, _missing,
        XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
    workbook.Close(true, _missing, _missing);
    application.Quit();
    Marshal.ReleaseComObject(worksheets);
    Marshal.ReleaseComObject(workbook);
    Marshal.ReleaseComObject(application);
}

What prompted me to ask this question is that, being the LINQ devotee I am, I really want to do something like this:

var worksheetNames = worksheets.Cast<Worksheet>().Select(ws => ws.Name);

...but I'm concerned I'll end up with memory leaks or ghost processes if I don't release each worksheet (ws) object.

Any insight on this would be appreciated.

Update

Based on the answers so far, it sounds like I really do need to release every single com object I create. I took the opportunity to build a ComObjectManager class to make it a little easier to deal with this headache. You have to remember to use the Get() method each time you instantiate a new com object, but if you do, it will take care of everything else for you. Please let me know if you see any problems with it (or edit and leave a comment if you are able). Here's the code:

public class ComObjectManager : IDisposable
{
    private Stack<object> _comObjects = new Stack<object>();

    public TComObject Get<TComObject>(Func<TComObject> getter)
    {
        var comObject = getter();
        _comObjects.Push(comObject);
        return comObject;
    }

    public void Dispose()
    {
        // these two lines of code will dispose of any unreferenced COM objects
        GC.Collect();
        GC.WaitForPendingFinalizers();

        while (_comObjects.Count > 0)
            Marshal.ReleaseComObject(_comObjects.Pop());
    }
}

Here's a usage example:

public void CreateExcelWorkbookWithSingleSheet()
{
    using (var com = new ComObjectManager())
    {
        var application = com.Get<ApplicationClass>(() => new ApplicationClass());
        var workbook = com.Get<Workbook>(() => application.Workbooks.Add(_missing));
        var worksheets = com.Get<Sheets>(() => workbook.Worksheets);
        for (var worksheetIndex = 1; worksheetIndex < worksheets.Count; worksheetIndex++)
        {
            var worksheet = com.Get<WorksheetClass>(() => (WorksheetClass)worksheets[worksheetIndex]);
            worksheet.Delete();
        }
        workbook.SaveAs(
            WorkbookPath, _missing, _missing, _missing, _missing, _missing,
            XlSaveAsAccessMode.xlExclusive, _missing, _missing, _missing, _missing, _missing);
        workbook.Close(true, _missing, _missing);
        application.Quit();
    }
}

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

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

发布评论

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

评论(3

遗弃M 2024-09-09 22:28:45

我相信您必须对每个 COM 对象调用 ReleaseComObject。由于它们没有被垃圾收集,因此父子层次结构并没有真正进入等式:即使您释放父对象,它也不会减少任何子对象的引用计数。

I believe you would have to call ReleaseComObject on each COM object. Since they're not garbage-collected, the parent-child hierarchy doesn't really come into the equation: even if you release the parent object it does not decrement the reference count on any child objects.

故事还在继续 2024-09-09 22:28:45

您应该对代码中使用的每个 COM 对象调用 Marshal.ReleaseComObject,而不仅仅是主应用程序对象。

You should call Marshal.ReleaseComObject on every COM object you use in your code, not just the main application object.

千纸鹤 2024-09-09 22:28:45

不。您不必释放单个 COM 对象。请参阅此答案:使用 IDisposable 清理 Excel 互操作对象

总结答案:
垃圾收集器会在需要的时候处理它们,除非你的程序崩溃了。您需要注意的是:

  1. 在调试模式下运行应用程序可能会延迟/阻止清理
    COM 对象。
  2. 停止调试器(从 Visual Studio 中)将
    防止 COM 对象的清理。就好像您使应用程序崩溃了一样。
  3. 如果正确关闭调试的应用程序,您将看到所有 COM 对象都被释放。此外,在发布模式下运行应用程序并正确关闭它也将释放所有 COM 对象。

现在,如果您想在方法调用结束后立即释放所有 COM 对象,那么您可以简单地调用
GC.Collect();
GC.WaitForPendingFinalizers();

但是您需要在创建 COM 对象的方法之外调用此方法。同样,如果您正在调试应用程序,这可能无法按预期工作,但它可以在发布模式下工作。

No. You don't have to release a single COM object. See this answer: Clean up Excel Interop Objects with IDisposable

To sum up the answer:
The garbage collector will take care of them when it feels like it except if your program crash. What you need to be aware is:

  1. Running your app in DEBUG mode might delay/prevent the cleanup of
    COM object.
  2. Stopping the debugger (from within visual studio) will
    prevent the clean up of COM object. It is as if you crashed the app.
  3. If you close the debugged app properly, you will see that all COM objects are released. Also, running your app in Release mode and closing it properly will also release all COM objects.

Now if you want to release all COM object right after your method call ended, then you can Simply call
GC.Collect();
GC.WaitForPendingFinalizers();

But you need to call this OUTSIDE the method who created the COM object. Again, this might not work as expected if you are debugging the app but it will work in Release mode.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文