使用Wrapper对象正确清理Excel互操作对象
所有这些问题:
- 通过 .NET 关闭时 Excel 2007 挂起
- < a href="https://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects-in-c#159419">如何在 C# 中正确清理 Excel 互操作对象< /a>
- 如何正确清理 C# 中的互操作对象
解决 C# 使用 Excel COM 对象后无法正确释放它们的问题。解决此问题主要有两个方向:
- 当不再使用 Excel 时,终止 Excel 进程。
- 请注意首先将使用的每个 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:
- Excel 2007 Hangs When Closing via .NET
- How to properly clean up Excel interop objects in C#
- How to properly clean up interop objects in C#
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:
- Kill the Excel process when Excel is not used anymore.
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
建议不要将清理代码放入终结器中,因为与 C++ 中的析构函数不同,它不会被确定性地调用。它可能会在对象超出范围后不久被调用。可能需要一个小时。它可能永远不会被调用。一般来说,如果您想处置非托管对象,您应该使用 IDisposable 模式而不是终结器。
您链接到的这个解决方案尝试通过显式调用垃圾收集器并等待终结器完成来解决该问题。一般情况下确实不建议这样做,但对于这种特殊情况,有些人认为这是一个可接受的解决方案,因为很难跟踪创建的所有临时非托管对象。但明确清理是正确的方法。然而考虑到这样做的困难,这种“黑客”可能是可以接受的。请注意,此解决方案可能比您提出的想法更好。
相反,如果您想尝试显式清理,“不要对 COM 对象使用两个点”准则将帮助您记住保留对您创建的每个对象的引用,以便在完成后可以清理它们。
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.
我们使用 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
查看我的项目 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.
我会做什么:
用例。请注意对 Quit 的调用,这似乎是结束进程所必需的:
What I'd do:
Usage case. Note the call to Quit, which seems to be necessary to make the process end:
无论如何,codeplex 上的 Excel 刷新服务 使用以下逻辑:
For what it's worth, the Excel Refresh Service on codeplex uses this logic: