Excel interop COM doesn't close

前端 未结 4 629
青春惊慌失措
青春惊慌失措 2021-01-22 03:27

I have some code that opens a spreadsheet, reads some values, and then closes the sheet. I need to do this for multiple files. The problem I\'m having is that the Excel applic

4条回答
  •  感动是毒
    2021-01-22 04:20

    You can use a wrapper object around the actual COM object that implements IDisposable, so that it can be used with C#'s using statement.

    The benefit of this is that it'll promote readable code and it'll work for any COM object. Here's an example with runtime dispatching:

    using System;
    using System.Reflection;
    using System.Runtime.InteropServices;
    
    public class ComRef : IDisposable where T : class
    {
        private T reference;
    
        public T Reference
        {
            get
            {
                return reference;
            }
        }
    
        public ComRef(T o)
        {
            reference = o;
        }
    
        public void Dispose()
        {
            if (reference != null)
            {
                Marshal.ReleaseComObject(reference);
                reference = null;
            }
        }
    }
    
    public class Test
    {
        static void Main()
        {
            Type excelAppType = Type.GetTypeFromProgID("Excel.Application");
            using (var comRef = new ComRef(Activator.CreateInstance(excelAppType)))
            {
                var excel = comRef.Reference;
                // ...
                excel.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, excel, null);
            }
        }
    }
    
    
    

    If, however, you already have imported Excel's type library, or any other type library for that matter, you might want something a bit more friendly:

    public class CoClassComRef : ComRef where T : class, new()
    {
        public CoClassComRef() : base(new T())
        {
        }
    }
    
    public class Test
    {
        static void Main()
        {
            using (var comRef = new CoClassComRef())
            {
                var excel = comRef.Reference;
                // ...
                excel.Quit();
            }
        }
    }
    

    You should just make sure that you don't capture comRef.Reference to some field or variable that outlives the using statement.

    Note that I haven't given much thought about thread safety and a proper Dispose implementation. Thread safety isn't important if you only use a ComRef with using statements. A proper Dispose implementation would colaborate with a finalizer, but there's no need for that here, as using is basically a try-finally. If you use a ComRef not in a using statement and Dispose is not called, the ComRef will simply be garbage collected, and with it the underlying COM object, which will be released if only this ComRef was referencing it.

    Finally, I didn't use Marshal.FinalReleaseComObject, because that is made when you're absolutely sure you want to release the underlying COM object (at least, all references from the managed environment) no matter how many times it has (re)entered the managed world. However, if you feel lucky, you may just create a new constructor which also receives a boolean stating if FinalReleaseComObject should be called instead of ReleaseComObject. The first results on a web search for any of these methods will point to articles and blog posts detailing why they are usually evil, one more than the other.

    提交回复
    热议问题