前言:我的答案包含两种解决方案,因此阅读时请务必小心,不要错过任何内容。
对于如何卸载Excel实例,有不同的方法和建议,例如:
使用Marshal.FinalReleaseComObject()显式释放每个com对象(不要忘记隐式创建的com对象)。要释放每个创建的com对象,可以使用此处提到的2点规则:
如何正确清理Excel互操作对象?
调用GC.Collect()和GC.WaitForPendingFinalizers()可使CLR释放未使用的COM对象*(实际上,它可以工作,有关详细信息,请参见我的第二个解决方案)
检查com-server-application是否显示一个等待用户回答的消息框(尽管我不确定它是否可以阻止Excel关闭,但我听说过几次)
将WM_CLOSE消息发送到Excel主窗口
在单独的AppDomain中执行与Excel一起使用的功能。有人认为,卸载AppDomain后,Excel实例将关闭。
杀死所有在我们的excel互操作代码启动后实例化的excel实例。
但!有时,所有这些选项都无济于事或不合适!
例如,昨天我发现在我的其中一个函数(与excel一起使用)中,Excel在函数结束后一直运行。我尝试了一切!我彻底检查了整个功能10次,并为所有内容添加了Marshal.FinalReleaseComObject()!我也有GC.Collect()和GC.WaitForPendingFinalizers()。我检查了隐藏的消息框。我试图将WM_CLOSE消息发送到Excel主窗口。我在单独的AppDomain中执行了我的功能,然后卸载了该域。没有任何帮助!关闭所有excel实例的选项是不合适的,因为如果用户在我的函数执行期间手动启动另一个Excel实例(也可以使用Excel),则该实例也将被我的函数关闭。我敢打赌用户不会满意!因此,说实话,这是一个la脚的选择(没有冒犯的人)。解决方案:通过其主窗口的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(例如,该进程不再存在),则一种方法不会引发异常。 ,如果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()无效的最常见原因之一是在Debug模式下运行项目。在调试模式下,不再真正引用的对象将在方法结束之前不会被垃圾回收。
因此,如果您尝试使用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将关闭=)