Lời nói đầu: câu trả lời của tôi chứa hai giải pháp, vì vậy hãy cẩn thận khi đọc và đừng bỏ lỡ điều gì.
Có nhiều cách và lời khuyên khác nhau về cách làm cho cá thể Excel không tải, chẳng hạn như:
Phát hành rõ ràng MỌI đối tượng com với Marshal.FinalReleaseComObject () (không quên về các đối tượng com được tạo ngầm). Để phát hành mọi đối tượng com đã tạo, bạn có thể sử dụng quy tắc 2 dấu chấm được đề cập ở đây:
Làm cách nào để dọn sạch các đối tượng interop Excel đúng cách?
Gọi GC.Collect () và GC.WaitForPendingFinalulators () để làm cho CLR giải phóng các đối tượng com không sử dụng * (Trên thực tế, nó hoạt động, xem chi tiết giải pháp thứ hai của tôi)
Kiểm tra xem ứng dụng com-server có thể hiển thị hộp thông báo đang chờ người dùng trả lời hay không (mặc dù tôi không chắc nó có thể ngăn Excel đóng cửa không, nhưng tôi đã nghe về nó vài lần)
Gửi tin nhắn WM_CLOSE đến cửa sổ Excel chính
Thực thi chức năng hoạt động với Excel trong một AppDomain riêng. Một số người tin rằng phiên bản Excel sẽ bị đóng, khi AppDomain được tải.
Giết tất cả các trường hợp excel đã được khởi tạo sau khi mã xen kẽ excel của chúng tôi bắt đầu.
NHƯNG! Đôi khi tất cả các tùy chọn này chỉ không giúp đỡ hoặc không thể phù hợp!
Ví dụ, hôm qua tôi phát hiện ra rằng trong một trong các hàm của tôi (hoạt động với excel) Excel sẽ tiếp tục chạy sau khi hàm kết thúc. Tôi đã thử mọi cách! Tôi đã kiểm tra kỹ lưỡng toàn bộ hàm 10 lần và thêm Marshal.FinalReleaseComObject () cho mọi thứ! Tôi cũng đã có GC.Collect () và GC.WaitForPendingFinalulators (). Tôi đã kiểm tra các hộp tin nhắn ẩn. Tôi đã cố gắng gửi tin nhắn WM_CLOSE đến cửa sổ Excel chính. Tôi đã thực thi chức năng của mình trong một AppDomain riêng và dỡ tải tên miền đó. Không có gì giúp được! Tùy chọn đóng tất cả các phiên bản excel là không phù hợp, bởi vì nếu người dùng khởi động một phiên bản Excel khác theo cách thủ công, trong khi thực hiện chức năng của tôi cũng hoạt động với Excel, thì phiên bản đó cũng sẽ bị đóng bởi chức năng của tôi. Tôi cá là người dùng sẽ không vui! Vì vậy, thành thật mà nói, đây là một lựa chọn khập khiễng (không có kẻ xúc phạm).Giải pháp : Tiêu diệt quá trình excel bằng hWnd của cửa sổ chính của nó (đó là giải pháp đầu tiên).
Đây là mã đơn giản:
[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();
}
Như bạn có thể thấy tôi đã cung cấp hai phương thức, theo mẫu Try-Parse (tôi nghĩ rằng nó phù hợp ở đây): một phương thức không ném ngoại lệ nếu Quy trình không thể bị giết (ví dụ quy trình không còn tồn tại nữa) và một phương thức khác đưa ra ngoại lệ nếu Quá trình không bị giết. Điểm yếu duy nhất trong mã này là quyền bảo mật. Về mặt lý thuyết, người dùng có thể không có quyền để giết quá trình, nhưng trong 99,99% tất cả các trường hợp, người dùng có quyền như vậy. Tôi cũng đã thử nghiệm nó với một tài khoản khách - nó hoạt động hoàn hảo.
Vì vậy, mã của bạn, hoạt động với Excel, có thể trông như thế này:
int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);
Voila! Excel bị chấm dứt! :)
Ok, chúng ta hãy quay trở lại giải pháp thứ hai, như tôi đã hứa ở đầu bài.
Giải pháp thứ hai là gọi GC.Collect () và GC.WaitForPendingFinalulators (). Vâng, họ thực sự làm việc, nhưng bạn cần phải cẩn thận ở đây!
Nhiều người nói (và tôi đã nói) rằng gọi GC.Collect () không giúp được gì. Nhưng lý do nó không có ích là nếu vẫn còn các tham chiếu đến các đối tượng COM! Một trong những lý do phổ biến nhất khiến GC.Collect () không hữu ích là chạy dự án ở chế độ Gỡ lỗi. Trong các đối tượng chế độ gỡ lỗi không thực sự được tham chiếu nữa sẽ không được thu gom rác cho đến khi kết thúc phương thức.
Vì vậy, nếu bạn đã thử dùng GC.Collect () và GC.WaitForPendingFinalulators () và nó không có ích, hãy thử làm như sau:
1) Thử chạy dự án của bạn ở chế độ Phát hành và kiểm tra xem Excel có đóng đúng không
2) Gói phương thức làm việc với Excel theo một phương pháp riêng. Vì vậy, thay vì một cái gì đó như thế này:
void GenerateWorkbook(...)
{
ApplicationClass xl;
Workbook xlWB;
try
{
xl = ...
xlWB = xl.Workbooks.Add(...);
...
}
finally
{
...
Marshal.ReleaseComObject(xlWB)
...
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
bạn viết:
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)
...
}
}
Bây giờ, Excel sẽ đóng =)