简体   繁体   English

*每个* Excel互操作对象是否需要使用Marshal.ReleaseComObject释放?

[英]Does *every* Excel interop object need to be released using Marshal.ReleaseComObject?

Edit 编辑

Please see also How do I properly clean up Excel interop objects? 另请参阅如何正确清理Excel互操作对象? . I recently came across this question, and it provided a lot of insight into the problem of how to properly dispose of COM objects. 我最近遇到了这个问题,它提供了很多关于如何正确处理COM对象的问题的见解。 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. 绝对检查超出第一个(标记的)答案,因为其他答案超出了简单的“不使用两个点”和“为每个com对象使用ReleaseComObject ”的建议。

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. 我首先重新审视了这个问题,因为我意识到尽管注册和处理所有COM对象非常彻底,但我的Excel实例仍然没有得到妥善处理。 It turns out, there are ways COM objects can be created that are completely non-obvious (ie, you can miss COM objects even if you never use two dots). 事实证明,有些方法可以创建完全不明显的COM对象(即,即使您从不使用两个点,也可以错过COM对象)。 In addition, even if you are thorough, if your project grows beyond a certain size, the chance of missing a COM object approaches 100%. 此外,即使您是彻底的,如果您的项目增长超过一定的大小,错过COM对象的机会接近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. 上面链接的问题的答案提供了一些其他技术,以确保Excel实例肯定关闭。 Meanwhile, I've made a small (but significant) update to my ComObjectManager (below) to reflect what I learned from the question linked above. 同时,我对我的ComObjectManager (下面)做了一个小的(但很重要的)更新,以反映我从上面链接的问题中学到的东西。

Original Question 原始问题

I've seen several examples where Marshal.ReleaseComObject() is used with Excel Interop objects (ie, objects from namespace Microsoft.Office.Interop.Excel), but I've seen it used to various degrees. 我看过几个例子,其中Marshal.ReleaseComObject()与Excel Interop对象(即名称空间Microsoft.Office.Interop.Excel中的对象)一起使用,但我已经看到它用于不同程度。

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: 是什么促使我问这个问题的是,作为我的LINQ奉献者,我真的想做这样的事情:

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. ...但我担心如果我不释放每个工作表( ws )对象,我最终会遇到内存泄漏或重影过程。

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. 基于到目前为止的答案,听起来我真的需要释放我创建的每个com对象。 I took the opportunity to build a ComObjectManager class to make it a little easier to deal with this headache. 我借此机会构建了一个ComObjectManager类,以便更轻松地解决这个问题。 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. 每次实例化一个新的com对象时都必须记住使用Get()方法,但如果这样做,它将为您处理其他所有事情。 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();
    }
}

I believe you would have to call ReleaseComObject on each COM object. 我相信你必须在每个COM对象上调用ReleaseComObject。 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. 由于它们不是垃圾收集的,因此父子层次结构并不真正进入等式:即使释放父对象,它也不会减少任何子对象的引用计数。

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

No. You don't have to release a single COM object. 不需要。您不必释放单个COM对象。 See this answer: Clean up Excel Interop Objects with IDisposable 请参阅以下答案: 使用IDisposable清理Excel Interop对象

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. 在DEBUG模式下运行您的应用程序可能会延迟/阻止COM对象的清理。
  2. Stopping the debugger (from within visual studio) will prevent the clean up of COM object. 停止调试器(从visual studio中)将阻止清理COM对象。 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. 如果正确关闭调试的应用程序,您将看到所有COM对象都已释放。 Also, running your app in Release mode and closing it properly will also release all COM objects. 此外,在发布模式下运行您的应用程序并正确关闭它也将释放所有COM对象。

Now if you want to release all COM object right after your method call ended, then you can Simply call GC.Collect(); GC.WaitForPendingFinalizers(); 现在,如果要在方法调用结束后立即释放所有COM对象,则可以简单地调用GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers();

But you need to call this OUTSIDE the method who created the COM object. 但是你需要在这个OUTSIDE中调用创建COM对象的方法。 Again, this might not work as expected if you are debugging the app but it will work in Release mode. 同样,如果您正在调试应用程序,它可能无法按预期工作,但它将在发布模式下工作。

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

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