我在C#(ApplicationClass
)中使用Excel互操作,并将以下代码放在我的finally子句中:
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();
虽然这种方法很有效,但即使在我关闭Excel后,Excel.exe
进程仍然在后台。它仅在我的应用程序手动关闭后才会发布。
我做错了什么,或者是否有其他方法可以确保正确处理互操作对象?
答案 0 :(得分:664)
Excel不会退出,因为您的应用程序仍然保持对COM对象的引用。
我猜你是在调用COM对象的至少一个成员而不将其分配给变量。
对我来说,我直接使用 excelApp.Worksheets 对象而不将其分配给变量:
Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);
我不知道内部C#为工作表 COM对象创建了一个包装器,它没有被我的代码释放(因为我不知道它),这就是为什么Excel没有卸载。
我在this page上找到了我的问题的解决方案,这对于在C#中使用COM对象也有一个很好的规则:
切勿对COM对象使用两个点。
因此,有了这些知识,正确的做法是:
Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
POST MORTEM UPDATE:
我希望每个读者都能仔细阅读Hans Passant的这个答案,因为它解释了我和许多其他开发人员陷入困境的陷阱。当我几年前写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论。我为了历史而保持我的答案不变,但请阅读此链接并不要采用“两点”的方式:Understanding garbage collection in .NET和Clean up Excel Interop Objects with IDisposable
答案 1 :(得分:269)
您可以干净利落地发布Excel应用程序对象,但必须小心。
为您访问的绝对每个COM对象维护命名引用的建议,然后通过Marshal.FinalReleaseComObject()
显式释放它在理论上是正确的,但不幸的是,在实践中很难管理。如果有人在任何地方滑动并使用“两个点”,或通过for each
循环或任何其他类似命令迭代单元格,那么您将拥有未引用的COM对象并冒着挂起的风险。在这种情况下,无法在代码中找到原因;你必须通过眼睛审查你的所有代码,并希望找到原因,这个任务对于大型项目来说几乎是不可能的。
好消息是,您实际上不必维护对您使用的每个COM对象的命名变量引用。相反,调用GC.Collect()
然后GC.WaitForPendingFinalizers()
来释放您没有引用的所有(通常是次要的)对象,然后显式释放您持有命名变量引用的对象。
您还应该按重要性的相反顺序发布命名引用:首先是范围对象,然后是工作表,工作簿,最后是Excel应用程序对象。
例如,假设您有一个名为xlRng
的Range对象变量,名为xlSheet
的Worksheet变量,名为xlBook
的Workbook变量和名为{{1}的Excel应用程序变量然后你的清理代码可能如下所示:
xlApp
在大多数代码示例中,您将看到从.NET清理COM对象,// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);
xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);
xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);
和GC.Collect()
调用是TWICE,如下所示:
GC.WaitForPendingFinalizers()
但是,除非您使用的是Visual Studio Tools for Office(VSTO),否则不需要这样做,因为它使用终结器来导致在终结队列中提升整个对象图。在 next 垃圾收集之前,这些对象不会被释放。但是,如果您不使用VSTO,则应该只能拨打GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
和GC.Collect()
一次。
我知道明确地调用GC.WaitForPendingFinalizers()
是一个禁忌(当然这样做两次听起来非常痛苦),但说实话,没有办法解决它。通过正常操作,您将生成隐藏的对象,除此之外,您不能通过调用GC.Collect()
以外的任何其他方式释放隐藏的对象。
这是一个复杂的话题,但这就是它的全部内容。为清理过程建立此模板后,您可以正常编码,无需包装等。: - )
我在这里有一个教程:
Automating Office Programs with VB.Net / COM Interop
它是为VB.NET编写的,但不要因此而被推迟,其原理与使用C#时完全相同。
答案 2 :(得分:204)
前言:我的回答包含两个解决方案,因此在阅读时要小心,不要错过任何内容。
有关如何卸载Excel实例的不同方法和建议,例如:
显式释放每个com对象
与Marshal.FinalReleaseComObject()
(不要忘记隐含的
创建了com对象)。要发布
每个创建的com对象,您都可以使用
这里提到的2点规则:
How do I properly clean up Excel interop objects?
调用GC.Collect()和 GC.WaitForPendingFinalizers()来制作 CLR发布未使用的com-objects *(实际上,它有效,请参阅我的第二个解决方案以获取详细信息)
检查是否有com-server-application 也许会显示一个消息框等待 用户回答(虽然我不是 确定它可以阻止Excel 关闭,但我听说了几个 倍)
向主要发送WM_CLOSE消息 Excel窗口
执行有效的功能 使用Excel在单独的AppDomain中。 有些人相信Excel实例 将在AppDomain时关闭 卸载。
杀死我们的excel-interoping代码启动后实例化的所有excel实例。
但是!有时候所有这些选项都无济于事或不合适!
例如,昨天我发现在我的一个函数中(使用excel)Excel在函数结束后继续运行。我尝试了一切!我彻底检查了整个函数10次,并为所有内容添加了Marshal.FinalReleaseComObject()!我还有GC.Collect()和GC.WaitForPendingFinalizers()。我检查了隐藏的消息框。我试图将WM_CLOSE消息发送到主Excel窗口。我在一个单独的AppDomain中执行了我的函数并卸载了该域。什么都没有帮助!关闭所有excel实例的选项是不合适的,因为如果用户手动启动另一个Excel实例,在执行我的函数期间也可以使用Excel,那么该实例也将被我的函数关闭。我打赌用户不会高兴!所以,老实说,这是一个蹩脚的选择(没有冒犯的人)。所以我花了几个小时才发现一个好的(以我的拙见)解决方案:通过hWnd的主窗口杀死excel进程(这是第一个解决方案)。
以下是简单的代码:
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if(processID == 0) return false;
try
{
Process.GetProcessById((int)processID).Kill();
}
catch (ArgumentException)
{
return false;
}
catch (Win32Exception)
{
return false;
}
catch (NotSupportedException)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
return true;
}
/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running).
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
if (processID == 0)
throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
Process.GetProcessById((int)processID).Kill();
}
正如你所看到的,我提供了两种方法,根据Try-Parse模式(我认为这里是合适的):如果Process不能被杀死,一个方法不会抛出异常(例如,进程不能已存在),如果进程未被杀死,另一个方法抛出异常。此代码中唯一的弱点是安全权限。从理论上讲,用户可能没有权限终止该进程,但在99.99%的情况下,用户具有此类权限。我还使用访客帐户对其进行了测试 - 它运行良好。
因此,使用Excel的代码可能如下所示:
int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);
瞧! Excel终止了! :)
好的,让我们回到第二个解决方案,正如我在帖子开头所承诺的那样。 第二个解决方案是调用GC.Collect()和GC.WaitForPendingFinalizers()。是的,它们确实有效,但是你需要小心! 很多人说(我说)调用GC.Collect()没有帮助。但它无法帮助的原因是仍然有对COM对象的引用! GC.Collect()没有帮助的最常见原因之一是在调试模式下运行项目。在调试模式中,在方法结束之前,不再对实际引用的对象进行垃圾回收 因此,如果您尝试GC.Collect()和GC.WaitForPendingFinalizers()并且它没有帮助,请尝试执行以下操作:
1)尝试在发布模式下运行项目并检查Excel是否正确关闭
2)以单独的方法包装使用Excel的方法。 所以,而不是像这样的东西:
void GenerateWorkbook(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
你写道:
void GenerateWorkbook(...)
{
try
{
GenerateWorkbookInternal(...);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void GenerateWorkbookInternal(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
}
}
现在,Excel将关闭=)
答案 3 :(得分:46)
更新:添加了C#代码,并链接到Windows作业
我花了一些时间试图弄清楚这个问题,当时XtremeVBTalk是最活跃和最敏感的。这是我原始帖子Closing an Excel Interop process cleanly, even if your application crashes的链接。以下是帖子的摘要,以及复制到这篇文章的代码。
Application.Quit()
和Process.Kill()
的Interop流程大部分工作,但如果应用程序崩溃灾难则会失败。即如果应用程序崩溃,Excel进程仍将运行松散。我发现这是一个干净的解决方案,因为操作系统正在进行清理工作。您所要做的就是注册 Excel流程。
Windows职务代码
包装Win32 API调用以注册Interop进程。
public enum JobObjectInfoType
{
AssociateCompletionPortInformation = 7,
BasicLimitInformation = 2,
BasicUIRestrictions = 4,
EndOfJobTimeInformation = 6,
ExtendedLimitInformation = 9,
SecurityLimitInformation = 5,
GroupInformation = 11
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public Int64 PerProcessUserTimeLimit;
public Int64 PerJobUserTimeLimit;
public Int16 LimitFlags;
public UInt32 MinimumWorkingSetSize;
public UInt32 MaximumWorkingSetSize;
public Int16 ActiveProcessLimit;
public Int64 Affinity;
public Int16 PriorityClass;
public Int16 SchedulingClass;
}
[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
public UInt64 ReadOperationCount;
public UInt64 WriteOperationCount;
public UInt64 OtherOperationCount;
public UInt64 ReadTransferCount;
public UInt64 WriteTransferCount;
public UInt64 OtherTransferCount;
}
[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UInt32 ProcessMemoryLimit;
public UInt32 JobMemoryLimit;
public UInt32 PeakProcessMemoryUsed;
public UInt32 PeakJobMemoryUsed;
}
public class Job : IDisposable
{
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr CreateJobObject(object a, string lpName);
[DllImport("kernel32.dll")]
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
private IntPtr m_handle;
private bool m_disposed = false;
public Job()
{
m_handle = CreateJobObject(null, null);
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
extendedInfo.BasicLimitInformation = info;
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
private void Dispose(bool disposing)
{
if (m_disposed)
return;
if (disposing) {}
Close();
m_disposed = true;
}
public void Close()
{
Win32.CloseHandle(m_handle);
m_handle = IntPtr.Zero;
}
public bool AddProcess(IntPtr handle)
{
return AssignProcessToJobObject(m_handle, handle);
}
}
关于构造函数代码的注意事项
info.LimitFlags = 0x2000;
。 0x2000
是JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
枚举值,此值由MSDN定义为:导致与作业关联的所有进程在终止时终止 该工作的最后一个句柄已经关闭。
额外的Win32 API调用以获取进程ID(PID)
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
使用代码
Excel.Application app = new Excel.ApplicationClass();
Job job = new Job();
uint pid = 0;
Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
job.AddProcess(Process.GetProcessById((int)pid).Handle);
答案 4 :(得分:35)
这适用于我正在进行的项目:
excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;
我们了解到,当您完成Excel COM对象的每个引用时,将其设置为null非常重要。这包括细胞,表格和一切。
答案 5 :(得分:29)
需要释放Excel命名空间中的任何内容。期间强>
你无法做到:
Worksheet ws = excel.WorkBooks[1].WorkSheets[1];
你必须要做
Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];
然后释放物体。
答案 6 :(得分:19)
首先 - 在执行Excel互操作时,从不必须致电Marshal.ReleaseComObject(...)
或Marshal.FinalReleaseComObject(...)
。这是一个令人困惑的反模式,但是有关此的任何信息,包括来自Microsoft,表明您必须从.NET手动释放COM引用的任何信息都是不正确的。事实是.NET运行时和垃圾收集器正确地跟踪和清理COM引用。对于你的代码,这意味着你可以删除顶部的整个`while(...)循环。
其次,如果要确保在进程结束时清除对进程外COM对象的COM引用(以便Excel进程关闭),则需要确保垃圾收集器运行。您可以通过拨打GC.Collect()
和GC.WaitForPendingFinalizers()
来正确执行此操作。调用这两次是安全的,并确保周期也被清理干净(虽然我不确定它是否需要,并且会欣赏一个显示这一点的例子。)
第三,当在调试器下运行时,本地引用将被人为地保持活动直到方法结束(以便局部变量检查起作用)。因此,GC.Collect()
调用对于从同一方法清除rng.Cells
之类的对象无效。您应该将执行COM interop的代码从GC清理拆分为单独的方法。 (对于我来说,这是一个重要的发现,来自@nightcoder发布的答案的一部分。)
因此,一般模式是:
Sub WrapperThatCleansUp()
' NOTE: Don't call Excel objects in here...
' Debugger would keep alive until end, preventing GC cleanup
' Call a separate function that talks to Excel
DoTheWork()
' Now let the GC clean up (twice, to clean up cycles too)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
Sub DoTheWork()
Dim app As New Microsoft.Office.Interop.Excel.Application
Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
app.Visible = True
For i As Integer = 1 To 10
worksheet.Cells.Range("A" & i).Value = "Hello"
Next
book.Save()
book.Close()
app.Quit()
' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub
关于这个问题有很多虚假信息和混淆,包括MSDN和Stack Overflow上的很多帖子(尤其是这个问题!)。
最终让我深入了解并找出正确建议的是博客帖子 Marshal.ReleaseComObject Considered Dangerous 以及发现调试器中存在的引用保持活动的问题,这让我早先感到困惑测试
答案 7 :(得分:18)
I found一个有用的通用模板,可以帮助实现COM对象的正确处理模式,当它们超出范围时需要调用Marshal.ReleaseComObject:
<强>用法:强>
using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
// do something with your workbook....
}
}
finally
{
excelApplicationWrapper.ComObject.Quit();
}
}
<强>模板:强>
public class AutoReleaseComObject<T> : IDisposable
{
private T m_comObject;
private bool m_armed = true;
private bool m_disposed = false;
public AutoReleaseComObject(T comObject)
{
Debug.Assert(comObject != null);
m_comObject = comObject;
}
#if DEBUG
~AutoReleaseComObject()
{
// We should have been disposed using Dispose().
Debug.WriteLine("Finalize being called, should have been disposed");
if (this.ComObject != null)
{
Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
}
//Debug.Assert(false);
}
#endif
public T ComObject
{
get
{
Debug.Assert(!m_disposed);
return m_comObject;
}
}
private string ComObjectName
{
get
{
if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
{
return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
}
return null;
}
}
public void Disarm()
{
Debug.Assert(!m_disposed);
m_armed = false;
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
#if DEBUG
GC.SuppressFinalize(this);
#endif
}
#endregion
protected virtual void Dispose(bool disposing)
{
if (!m_disposed)
{
if (m_armed)
{
int refcnt = 0;
do
{
refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
} while (refcnt > 0);
m_comObject = default(T);
}
m_disposed = true;
}
}
}
<强>参考:强>
http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/
答案 8 :(得分:15)
我不敢相信这个问题困扰了这个世界5年....如果你已经创建了一个应用程序,你需要先关闭它然后再删除链接。
objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
关闭时
objBook.Close(true, Type.Missing, Type.Missing);
objExcel.Application.Quit();
objExcel.Quit();
当您新建一个Excel应用程序时,它会在后台打开一个Excel程序。在释放链接之前,您需要命令excel程序退出,因为该Excel程序不是您的直接控制的一部分。因此,如果链接被释放,它将保持打开状态!
好好编程每个人~~
答案 9 :(得分:12)
普通开发人员,您的解决方案都不适用于我, 所以我决定实施一个新的技巧。
首先说明“我们的目标是什么?” =&GT; “在任务经理工作后,不要看excel对象”
确定。不要挑战并开始摧毁它,但考虑不要破坏并行运行的其他实例os Excel。
因此,获取当前处理器的列表并获取EXCEL进程的PID,然后在完成工作后,我们在进程列表中有一个具有唯一PID的新guest虚拟机,找到并销毁该文件。
&LT;请记住,在您的Excel工作期间,任何新的Excel工艺都将被检测为新的并销毁&gt; &LT;更好的解决方案是捕获新创建的excel对象的PID,然后销毁&gt;
Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
if (p.ProcessName == "EXCEL")
excelPID.Add(p.Id);
.... // your job
prs = Process.GetProcesses();
foreach (Process p in prs)
if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
p.Kill();
这解决了我的问题,也希望你的问题。
答案 10 :(得分:10)
这看起来确实过于复杂了。根据我的经验,只有三个关键的事情可以让Excel正确关闭:
1:确保没有对您创建的Excel应用程序的剩余引用(您应该只有一个;将其设置为null
)
2:致电GC.Collect()
3:必须关闭Excel,方法是由用户手动关闭程序,或者在Excel对象上调用Quit
。 (请注意,Quit
的功能就像用户试图关闭程序一样,如果有未保存的更改,则会显示确认对话框,即使Excel不可见。用户可以按取消,然后Excel将没有被关闭。)
1需要在2之前发生,但3可以随时发生。
实现此功能的一种方法是使用您自己的类包装互操作Excel对象,在构造函数中创建互操作实例,并使用Dispose实现IDisposable,如
if (!mDisposed) {
mExcel = null;
GC.Collect();
mDisposed = true;
}
这将从你的程序方面清除excel。关闭Excel后(由用户手动或通过调用Quit
),该过程将消失。如果该程序已经关闭,则该过程将在GC.Collect()
电话上消失。
(我不确定它有多重要,但您可能希望在GC.WaitForPendingFinalizers()
调用后进行GC.Collect()
调用,但不一定要摆脱Excel进程。)
多年来,这对我没有任何问题。请记住,虽然这有效,但实际上你必须优雅地关闭才能工作。如果在清理Excel之前中断程序(通常在调试程序时点击“停止”),您仍会累积excel.exe进程。
答案 11 :(得分:9)
我传统上遵循VVS's answer中的建议。但是,为了使这个答案与最新选项保持同步,我认为我未来的所有项目都将使用“NetOffice”库。
NetOffice是Office PIA的完全替代品,完全与版本无关。它是Managed COM包装器的集合,可以处理在使用.NET中的Microsoft Office时经常导致此类令人头疼的清理。
一些关键功能是:
我与该项目没有任何关系;我真的很欣赏头痛的严重减少。
答案 12 :(得分:9)
尝试后
GC.Collect()
和GC.WaitForPendingFinalizers()
对我有用的最终解决方案是移动一组
GC.Collect();
GC.WaitForPendingFinalizers();
我们将函数的末尾添加到包装器中,如下所示:
private void FunctionWrapper(string sourcePath, string targetPath)
{
try
{
FunctionThatCallsExcel(sourcePath, targetPath);
}
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
答案 13 :(得分:9)
要添加Excel不关闭的原因,即使在读取,创建时为每个对象创建直接引用,也是“For”循环。
For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
objWorkBook.Close 'or whatever
FinalReleaseComObject(objWorkBook)
objWorkBook = Nothing
Next
'The above does not work, and this is the workaround:
For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
objTempWorkBook.Saved = True
objTempWorkBook.Close(False, Type.Missing, Type.Missing)
FinalReleaseComObject(objTempWorkBook)
objTempWorkBook = Nothing
Next
答案 14 :(得分:9)
这里接受的答案是正确的,但也请注意,不仅需要避免“两点”引用,还需要避免通过索引检索的对象。您也不需要等到程序结束后才能清理这些对象,最好在可能的情况下创建一个在它们完成后立即清理它们的函数。这是我创建的一个函数,它分配一个名为xlStyleHeader
的Style对象的一些属性:
public Excel.Style xlStyleHeader = null;
private void CreateHeaderStyle()
{
Excel.Styles xlStyles = null;
Excel.Font xlFont = null;
Excel.Interior xlInterior = null;
Excel.Borders xlBorders = null;
Excel.Border xlBorderBottom = null;
try
{
xlStyles = xlWorkbook.Styles;
xlStyleHeader = xlStyles.Add("Header", Type.Missing);
// Text Format
xlStyleHeader.NumberFormat = "@";
// Bold
xlFont = xlStyleHeader.Font;
xlFont.Bold = true;
// Light Gray Cell Color
xlInterior = xlStyleHeader.Interior;
xlInterior.Color = 12632256;
// Medium Bottom border
xlBorders = xlStyleHeader.Borders;
xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
}
catch (Exception ex)
{
throw ex;
}
finally
{
Release(xlBorderBottom);
Release(xlBorders);
Release(xlInterior);
Release(xlFont);
Release(xlStyles);
}
}
private void Release(object obj)
{
// Errors are ignored per Microsoft's suggestion for this type of function:
// http://support.microsoft.com/default.aspx/kb/317109
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
}
catch { }
}
请注意,我必须将xlBorders[Excel.XlBordersIndex.xlEdgeBottom]
设置为变量才能清除它(不是因为两个点,它指的是不需要释放的枚举,而是因为对象我' m指的实际上是一个需要释放的Border对象。)
这种事情在标准应用程序中并不是必需的,它们可以很好地自行清理,但在ASP.NET应用程序中,如果你错过了其中一个,无论你多久调用一次垃圾收集器,Excel仍将在您的服务器上运行。
在编写此代码时监视任务管理器时需要注意细节和许多测试执行,但这样做可以省去拼命搜索代码页以找到错过的一个实例的麻烦。这在循环中工作时尤其重要,在循环中,您需要释放对象的EACH INSTANCE,即使它在每次循环时都使用相同的变量名称。
答案 15 :(得分:7)
我完全遵循了这一点......但我仍然遇到了1000次中的问题。谁知道为什么。是时候拿出锤子......
在实例化Excel Application类之后,我得到了刚刚创建的Excel进程。
excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();
然后,一旦我完成了上述所有COM清理工作,我确保该进程没有运行。如果它仍在运行,请将其杀死!
if (!process.HasExited)
process.Kill();
答案 16 :(得分:7)
¨°º¤ø“¸拍摄Excel处理并咀嚼泡泡糖¸”ø¤º°¨
public class MyExcelInteropClass
{
Excel.Application xlApp;
Excel.Workbook xlBook;
public void dothingswithExcel()
{
try { /* Do stuff manipulating cells sheets and workbooks ... */ }
catch {}
finally {KillExcelProcess(xlApp);}
}
static void KillExcelProcess(Excel.Application xlApp)
{
if (xlApp != null)
{
int excelProcessId = 0;
GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
Process p = Process.GetProcessById(excelProcessId);
p.Kill();
xlApp = null;
}
}
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}
答案 17 :(得分:6)
“永远不要在COM对象上使用两个点”是一个很好的经验法则,以避免COM引用的泄漏,但Excel PIA可能会导致泄漏,而不是一见钟情。
其中一种方法是订阅任何Excel对象模型的COM对象所暴露的任何事件。
例如,订阅Application类的WorkbookOpen事件。
COM类通过回调接口公开一组事件。为了订阅事件,客户端代码可以简单地注册实现回调接口的对象,并且COM类将调用其方法以响应特定事件。由于回调接口是COM接口,因此实现对象的任务是减少它为任何事件处理程序接收的任何COM对象的引用计数(作为参数)。
Excel PIA将Excel Application类的COM事件公开为常规.NET事件。每当客户端代码订阅一个.NET事件(强调'a')时,PIA就会创建一个实现回调接口的类的一个实例,并将其注册到Excel
因此,许多回调对象在Excel中注册,以响应来自.NET代码的不同订阅请求。每个事件订阅一个回调对象。
事件处理的回调接口意味着,PIA必须为每个.NET事件订阅请求订阅所有接口事件。它无法挑选。收到事件回调后,回调对象检查关联的.NET事件处理程序是否对当前事件感兴趣,然后调用处理程序或静默忽略回调。
所有这些回调对象都不会减少它们为任何回调方法接收的任何COM对象(作为参数)的引用计数(即使是对于被忽略的方法)。它们仅依靠CLR垃圾收集器来释放COM对象。
由于GC运行是不确定的,这可能会导致Excel进程停留的时间超过预期,并产生“内存泄漏”的印象。
目前唯一的解决方案是避免PIA的COM类事件提供程序,并编写自己的事件提供程序,确定性地释放COM对象。
对于Application类,可以通过实现AppEvents接口,然后使用IConnectionPointContainer interface向Excel注册实现来完成。 Application类(以及使用回调机制公开事件的所有COM对象)实现IConnectionPointContainer接口。
答案 18 :(得分:6)
您需要注意Excel对您正在运行的文化非常敏感。
您可能会发现在调用Excel函数之前需要将文化设置为EN-US。 这不适用于所有功能 - 但其中一些功能。
CultureInfo en_US = new System.Globalization.CultureInfo("en-US");
System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
string filePathLocal = _applicationObject.ActiveWorkbook.Path;
System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;
即使您使用VSTO,这也适用。
详细信息:http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369
答案 19 :(得分:5)
正如其他人所指出的那样,您需要为您使用的每个Excel对象创建一个显式引用,并在该引用上调用Marshal.ReleaseComObject,如this KB article中所述。您还需要使用try / finally确保始终调用ReleaseComObject,即使抛出异常也是如此。即而不是:
Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet
你需要做类似的事情:
Worksheets sheets = null;
Worksheet sheet = null
try
{
sheets = excelApp.Worksheets;
sheet = sheets(1);
...
}
finally
{
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (sheet != null) Marshal.ReleaseComObject(sheet);
}
如果希望Excel关闭,还需要在释放Application对象之前调用Application.Quit。
正如你所看到的,一旦你尝试做任何中等复杂的事情,这很快变得非常笨拙。我已经成功开发了.NET应用程序,其中包含一个简单的包装类,它包含了Excel对象模型的一些简单操作(打开工作簿,写入范围,保存/关闭工作簿等)。包装器类实现IDisposable,在它使用的每个对象上仔细实现Marshal.ReleaseComObject,并且不会公开地将任何Excel对象公开给应用程序的其余部分。
但是这种方法不能很好地适应更复杂的要求。
这是.NET COM Interop的一大缺陷。对于更复杂的场景,我会认真考虑用VB6或其他非托管语言编写ActiveX DLL,您可以将所有交互委托给Office等外部COM对象。然后,您可以从.NET应用程序中引用此ActiveX DLL,事情将变得更加容易,因为您只需要发布此一个引用。
答案 20 :(得分:4)
如果上面的所有内容都不起作用,请尝试让Excel有时间关闭它的表单:
app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();
...
FinalReleaseComObject(app);
答案 21 :(得分:3)
确保释放与Excel相关的所有对象!
我尝试了几种方法花了几个小时。所有这些都是很棒的想法,但我终于找到了我的错误:如果你没有发布所有对象,上面没有一种方法可以帮助你就像我的情况一样。确保释放包括范围一在内的所有对象!
Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);
选项在一起here。
答案 22 :(得分:2)
使用Word / Excel互操作应用程序时应该非常小心。在尝试了所有解决方案后,我们仍然在服务器上打开了很多“WinWord”进程(超过2000个用户)。
在解决了几个小时的问题之后,我意识到如果我同时在不同的线程上使用Word.ApplicationClass.Document.Open()
打开多个文档,IIS工作进程(w3wp.exe)会崩溃,所有WinWord进程都会打开!
所以我猜这个问题没有绝对的解决办法,但转而使用其他方法,例如Office Open XML开发。
答案 23 :(得分:2)
两点规则对我不起作用。在我的例子中,我创建了一个方法来清理我的资源,如下所示:
private static void Clean()
{
workBook.Close();
Marshall.ReleaseComObject(workBook);
excel.Quit();
CG.Collect();
CG.WaitForPendingFinalizers();
}
答案 24 :(得分:2)
有关发布COM对象的精彩文章是 2.5 Releasing COM Objects (MSDN)。
我提倡的方法是,如果它们是非局部变量,则将Excel.Interop引用置空,然后两次调用GC.Collect()
和GC.WaitForPendingFinalizers()
。本地范围的Interop变量将自动处理。
这样就无需为每个 COM对象保留命名参考。
以下是文章中的一个例子:
public class Test {
// These instance variables must be nulled or Excel will not quit
private Excel.Application xl;
private Excel.Workbook book;
public void DoSomething()
{
xl = new Excel.Application();
xl.Visible = true;
book = xl.Workbooks.Add(Type.Missing);
// These variables are locally scoped, so we need not worry about them.
// Notice I don't care about using two dots.
Excel.Range rng = book.Worksheets[1].UsedRange;
}
public void CleanUp()
{
book = null;
xl.Quit();
xl = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
这些话直接来自文章:
在几乎所有情况下,清空RCW引用并强制执行垃圾回收都会正确清理。如果你也调用GC.WaitForPendingFinalizers,垃圾收集将尽可能确定。也就是说,从第二次调用WaitForPendingFinalizers返回时,您将非常确定该对象何时被清除。作为替代方案,您可以使用Marshal.ReleaseComObject。但请注意,您不太可能需要使用此方法。
答案 25 :(得分:1)
正如有些人已经写过的那样,你关闭 Excel(对象)的方式并不重要;如何打开以及项目类型也很重要。
在WPF应用程序中,基本相同的代码在没有或几乎没有问题的情况下工作。
我有一个项目,其中为不同的参数值多次处理相同的Excel文件 - 例如基于泛型列表中的值解析它。
我将所有与Excel相关的函数放入基类,并将解析器放入子类(不同的解析器使用常用的Excel函数)。我不希望Excel为通用列表中的每个项目再次打开和关闭,所以我只在基类中打开它一次并在子类中关闭它。将代码移动到桌面应用程序时遇到问题。我尝试了很多上面提到的解决方案。 GC.Collect()
之前已经实施过了,是建议的两倍。
然后我决定将用于打开Excel的代码移动到子类。现在我不是只打开一次,而是创建一个新对象(基类)并为每个项打开Excel并在最后关闭它。存在一些性能损失,但基于多个测试,Excel进程正在关闭而没有问题(在调试模式下),因此也会删除临时文件。如果我得到一些更新,我会继续测试并写一些。
底线是:您还必须检查初始化代码,特别是如果您有许多类等。
答案 26 :(得分:1)
使用:
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
声明它,在finally
块中添加代码:
finally
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (excelApp != null)
{
excelApp.Quit();
int hWnd = excelApp.Application.Hwnd;
uint processID;
GetWindowThreadProcessId((IntPtr)hWnd, out processID);
Process[] procs = Process.GetProcessesByName("EXCEL");
foreach (Process p in procs)
{
if (p.Id == processID)
p.Kill();
}
Marshal.FinalReleaseComObject(excelApp);
}
}
答案 27 :(得分:1)
我的解决方案
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
private void GenerateExcel()
{
var excel = new Microsoft.Office.Interop.Excel.Application();
int id;
// Find the Excel Process Id (ath the end, you kill him
GetWindowThreadProcessId(excel.Hwnd, out id);
Process excelProcess = Process.GetProcessById(id);
try
{
// Your code
}
finally
{
excel.Quit();
// Kill him !
excelProcess.Kill();
}
答案 28 :(得分:1)
接受的答案对我不起作用。析构函数中的以下代码完成了这项工作。
if (xlApp != null)
{
xlApp.Workbooks.Close();
xlApp.Quit();
}
System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
答案 29 :(得分:1)
我的回答很晚,其唯一目的是通过完整的示例来支持Govert,Porkbutts和Dave Cousineau提出的解决方案。就像我们用德语所说的那样,从与COM无关的.NET世界自动化Excel或其他COM对象是一个“棘手的事情”,您可以轻松地发疯。我依靠以下步骤:
对于与Excel的每次交互,请获取Application接口的一个且仅一个本地实例ExcelApp
,并创建一个ExcelApp
所在的范围。这是必要的,因为CLR不会在对Excel的任何引用超出范围之前释放Excel的资源。在后台启动一个新的Excel流程。
执行功能,这些功能通过使用ExcelApp
来执行任务
通过Collection属性生成新对象,例如Workbook,
工作表和单元格。在这些功能中,无需关心
伏都教一个好点,两个坏点,不要试图获得参考
对于每个隐式创建的对象,不要
Marshall.ReleaseComObject
任何东西。那是垃圾的工作
收集。
在ExcelApp
范围内,调用这些函数并传递引用ExcelApp
。
在加载Excel实例时,请勿执行任何会绕过Quit
函数的用户操作,而该函数将再次卸载该实例。
使用Excel完成后,请在为处理Excel而设计的范围内调用单独的Quit
函数。这应该是该范围内的最后一条语句。
在运行我的应用程序之前,请打开任务管理器,并在“进程”选项卡中查看后台进程中的条目。当您启动程序时,一个新的Excel进程条目将出现在列表中,并保持在那里,直到5秒钟后线程再次唤醒。此后,将调用Quit函数,停止从后台进程列表中消失的Excel进程。
using System;
using System.Threading;
using Excel = Microsoft.Office.Interop.Excel;
namespace GCTestOnOffice
{
class Program
{
//Don't: private static Excel.Application ExcelApp = new Excel.Application();
private static void DoSomething(Excel.Application ExcelApp)
{
Excel.Workbook Wb = ExcelApp.Workbooks.Open(@"D:\Aktuell\SampleWorkbook.xlsx");
Excel.Worksheet NewWs = Wb.Worksheets.Add();
for (int i = 1; i < 10; i++)
{
NewWs.Cells[i, 1] = i;
}
Wb.Save();
}
public static void Quit(Excel.Application ExcelApp)
{
if (ExcelApp != null)
{
ExcelApp.Quit(); //Don't forget!!!!!
ExcelApp = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
static void Main(string[] args)
{
{
Excel.Application ExcelApp = new Excel.Application();
Thread.Sleep(5000);
DoSomething(ExcelApp);
Quit(ExcelApp);
//ExcelApp goes out of scope, the CLR can and will(!) release Excel
}
Console.WriteLine("Input a digit: ");
int k = Console.Read();
}
}
}
如果我将Main函数更改为
static void Main(string[] args)
{
Excel.Application ExcelApp = new Excel.Application();
DoSomething(ExcelApp);
Console.WriteLine("Input a digit: ");
int k = Console.Read();
Quit(ExcelApp);
}
用户可以不用输入数字,而是单击控制台的“关闭”按钮,然后我的Excel实例就可以幸福地生活了。因此,在您的Excel实例仍然顽固加载的情况下,您的清理功能可能不会出错,但是会被无法预料的用户操作所绕过。
如果Program类具有Excel实例的成员,则CLR不会在应用终止之前卸载Excel实例。这就是为什么我更喜欢不再需要范围的本地引用。
答案 30 :(得分:1)
我认为其中一些只是框架处理Office应用程序的方式,但我可能错了。在某些日子里,一些应用程序会立即清理进程,而其他日子似乎要等到应用程序关闭。一般情况下,我不再关注细节,只是确保在一天结束时没有任何额外的流程。
另外,也许我过度简化了事情,但我认为你可以......
objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();
就像我之前说的那样,我不倾向于关注Excel进程何时出现或消失的细节,但这通常对我有用。除了最短的时间之外,我也不喜欢保留Excel进程,但我可能只是偏执狂。
答案 31 :(得分:1)
我目前正致力于Office自动化,并偶然发现了一个适合我的解决方案。它很简单,不涉及杀死任何进程。
似乎只需循环浏览当前的活动流程,并以任何方式访问&#39;一个打开的Excel进程,将删除任何异常挂起的Excel实例。下面的代码只检查名称为&#39; Excel&#39;的进程,然后将进程的MainWindowTitle属性写入字符串。这种互动&#39;这个过程似乎让Windows赶上并中止了冻结的Excel实例。
我在加载项之前运行以下方法,我正在开发退出,因为它会触发它卸载事件。它每次都会删除任何挂起的Excel实例。老实说,我并不完全确定为什么会这样,但它适用于我,可以放在任何Excel应用程序的末尾,而不必担心双点,Marshal.ReleaseComObject,也不会杀死进程。我会对任何有效的建议感兴趣。
public static void SweepExcelProcesses()
{
if (Process.GetProcessesByName("EXCEL").Length != 0)
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
if (process.ProcessName.ToString() == "excel")
{
string title = process.MainWindowTitle;
}
}
}
}
答案 32 :(得分:1)
&#39;这看起来确实过于复杂了。根据我的经验,只有三个关键的事情可以让Excel正确关闭:
1:确保没有剩余的对你创建的excel应用程序的引用(你应该只有一个;将它设置为null)
2:调用GC.Collect()
3:必须通过用户手动关闭程序或在Excel对象上调用Quit来关闭Excel。 (请注意,退出将像用户尝试关闭程序一样运行,如果有未保存的更改,则会显示确认对话框,即使Excel不可见。用户可以按取消,然后Excel将不会关闭。)
1需要在2之前发生,但3可以随时发生。
实现此功能的一种方法是使用您自己的类包装互操作Excel对象,在构造函数中创建互操作实例,并使用Dispose实现IDisposable,如
这样可以清除程序中的优点。关闭Excel后(由用户手动或通过调用Quit),该过程将消失。如果程序已经关闭,那么该过程将在GC.Collect()调用中消失。
(我不确定它有多重要,但您可能需要在GC.Collect()调用之后调用GC.WaitForPendingFinalizers(),但并不是绝对必须摆脱Excel进程。)
多年来,这对我没有任何问题。请记住,虽然这有效,但实际上你必须优雅地关闭才能工作。如果在清理Excel之前中断程序(通常是通过点击&#34;停止&#34;在调试程序时),你仍然可以累积excel.exe进程。&#39;
答案 33 :(得分:0)
在其他答案中考虑的三种通用策略中,杀死excel
进程显然是黑客,而调用垃圾收集器则是一种残酷的shot弹枪方法,旨在补偿 COM -对象。经过大量实验并在与版本无关和后期绑定的包装器中重写了 COM 对象的管理,我得出的结论是,准确及时地调用Marshal.ReleaseComObject()
是最有效的和优雅的策略。而且不,您永远不需要FinalReleaseComObject()
,因为在编写良好的程序中,每个 COM 都是一次获取的,因此只需要对参考计数器进行一次递减。
一个人必须确保释放每个 COM 对象,最好是在不再需要它时就释放它。但是完全有可能在退出 Excel 应用程序后立即释放所有内容,而这只会增加内存使用量。只要没有松动或忘记释放 COM 对象, Excel 就会按预期关闭。
此过程中最简单,最明显的帮助是将每个互操作对象包装到实现IDisposable
的 .NET 类中,其中Dispose()
方法调用ReleaseComObject()
在其互操作对象上。根据{{3}}的建议,在析构函数中执行此操作是没有意义的,因为析构函数是不确定的。
下面显示的是我们的包装器方法,该方法通过绕过中间WorkSheet
成员从Cells
获取单元格。注意使用后它处理中间对象的方式:
public ExcelRange XCell( int row, int col)
{ ExcelRange anchor, res;
using( anchor = Range( "A1") )
{ res = anchor.Offset( row - 1, col - 1 ); }
return res;
}
下一步可能是一个简单的内存管理器,它将跟踪获得的每个 COM 对象,并确保在 Excel 退出后如果用户更喜欢进行交易,请释放该对象一些RAM的使用来简化代码。
答案 34 :(得分:0)
在VSTO加载项中的Application对象更新之后,我遇到了同样的问题,导致PowerPoint关闭。我在这里尝试了所有答案,但收效甚微。
这是我针对我的情况找到的解决方案-不要使用“新应用程序”,ThisAddIn的AddInBase基类已经具有“应用程序”的句柄。如果在需要的地方使用该手柄(必要时将其设置为静态),则无需担心清理它,PowerPoint不会挂在门上。
答案 35 :(得分:0)
这是一种非常简单的方法:
[DllImport("User32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
...
int objExcelProcessId = 0;
Excel.Application objExcel = new Excel.Application();
GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);
Process.GetProcessById(objExcelProcessId).Kill();
答案 36 :(得分:0)
通过Microsoft Excel 2016测试
一个经过实际测试的解决方案。
有关C#参考,请参见: https://stackoverflow.com/a/1307180/10442623
有关VB.net参考,请参见: https://stackoverflow.com/a/54044646/10442623
1包括班级工作
2实现该类以处理excel过程的适当处理
答案 37 :(得分:0)
我有一个主意,尝试杀死您打开的Excel进程:
在需要退出时,杀死oldProcessIds和nowProcessIds之间的除外ID。
private static Excel.Application GetExcelApp()
{
if (_excelApp == null)
{
var processIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
_excelApp = new Excel.Application();
_excelApp.DisplayAlerts = false;
_excelApp.Visible = false;
_excelApp.ScreenUpdating = false;
var newProcessIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
_excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
}
return _excelApp;
}
public static void Dispose()
{
try
{
_excelApp.Workbooks.Close();
_excelApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
_excelApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (_excelApplicationProcessId != default(int))
{
var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
process?.Kill();
_excelApplicationProcessId = default(int);
}
}
catch (Exception ex)
{
_excelApp = null;
}
}
答案 38 :(得分:0)
只是为这里列出的许多解决方案添加另一个解决方案,使用C ++ / ATL自动化(我想你可以使用类似于VB / C的东西#??)
Excel::_ApplicationPtr pXL = ...
:
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;
这对我来说就像是一种魅力...
答案 39 :(得分:0)
到目前为止,似乎所有答案都涉及其中一些:
这使我意识到这个问题有多困难:)
我一直在创建一个库以简化对Excel的访问,我正在努力确保使用它的人不会乱七八糟(手指交叉)。
而不是直接在Interop提供的接口上书写,我正在制作扩展方法以使生活更轻松。与ApplicationHelpers.CreateExcel()或workbook.CreateWorksheet(“mySheetNameThatWillBeValidated”)类似。当然,任何创建的东西都可能在以后的清理过程中导致问题,所以我实际上倾向于杀死这个过程作为最后的手段。然而,正确清理(第三种选择)可能是破坏性最小且控制最多的。
所以,在那种情况下,我想知道做出这样的事情是不是最好:
public abstract class ReleaseContainer<T>
{
private readonly Action<T> actionOnT;
protected ReleaseContainer(T releasible, Action<T> actionOnT)
{
this.actionOnT = actionOnT;
this.Releasible = releasible;
}
~ReleaseContainer()
{
Release();
}
public T Releasible { get; private set; }
private void Release()
{
actionOnT(Releasible);
Releasible = default(T);
}
}
我使用'Releasible'来避免与Disposable混淆。将此扩展到IDisposable应该很容易。
这样的实现:
public class ApplicationContainer : ReleaseContainer<Application>
{
public ApplicationContainer()
: base(new Application(), ActionOnExcel)
{
}
private static void ActionOnExcel(Application application)
{
application.Show(); // extension method. want to make sure the app is visible.
application.Quit();
Marshal.FinalReleaseComObject(application);
}
}
人们可以为各种COM对象做类似的事情。
在工厂方法中:
public static Application CreateExcelApplication(bool hidden = false)
{
var excel = new ApplicationContainer().Releasible;
excel.Visible = !hidden;
return excel;
}
我希望GC能够正确破坏每个容器,因此会自动拨打Quit
和Marshal.FinalReleaseComObject
。
评论?或者这是第三种问题的答案?
答案 40 :(得分:-1)
这是真正适用于我的唯一方式
1+1: True
1-1: True
2+9: True
3-12: True
31-10: True
11+11: True
+1: False
+2: False
+31: False
1+: False
7+: False
-1: False
-2: False
-31: False
1-: False
7-: False
答案 41 :(得分:-1)
Excel不能通过C ++或C#编程。 COM API专门用于与Visual Basic,VB.NET和VBA一起使用。
此页面上的所有代码示例都不是最佳的,原因很简单,每个调用必须跨越托管/非托管边界,并进一步忽略Excel COM API可以自由地使用隐藏式{{3指示RPC服务器正忙。
在我看来,自动化Excel的最佳方法是将数据收集到尽可能/可行的大数组中,然后将其发送到VBA函数或子函数(通过Application.Run
),然后执行任何所需的处理。此外 - 在致电Application.Run
时 - 请务必留意指示excel正忙的异常并重试拨打Application.Run
。