คำนำ: คำตอบของฉันมีสองวิธีดังนั้นระวังเมื่ออ่านและไม่พลาดอะไร
มีวิธีและคำแนะนำต่าง ๆ ในการทำให้การโหลดอินสแตนซ์ของ Excel เช่น:
ปล่อยวัตถุ com ทุกอย่างอย่างชัดเจนด้วย Marshal.FinalReleaseComObject () (ไม่ลืมเกี่ยวกับ com- วัตถุที่สร้างขึ้นโดยปริยาย) ในการปล่อยวัตถุ com ที่สร้างขึ้นทั้งหมดคุณสามารถใช้กฎของจุด 2 จุดที่กล่าวถึงที่นี่:
ฉันจะล้างวัตถุ Excel interop อย่างถูกต้องได้อย่างไร
เรียก GC.Collect () และ GC.WaitForPendingFinalizers () เพื่อทำให้ CLR ปล่อย com-objects ที่ไม่ได้ใช้ * (จริง ๆ แล้วมันใช้งานได้ดูรายละเอียดวิธีที่สองของฉัน)
ตรวจสอบว่าแอปพลิเคชันเซิร์ฟเวอร์เซิร์ฟเวอร์อาจแสดงกล่องข้อความรอให้ผู้ใช้ตอบ (แม้ว่าฉันไม่แน่ใจว่าสามารถป้องกัน Excel จากการปิด แต่ฉันได้ยินเกี่ยวกับมันสองสามครั้ง)
ส่งข้อความ WM_CLOSE ไปที่หน้าต่างหลักของ Excel
การใช้งานฟังก์ชันที่ทำงานกับ Excel ใน AppDomain แยกต่างหาก บางคนเชื่อว่าอินสแตนซ์ของ Excel จะถูกปิดเมื่อไม่มีการโหลด AppDomain
ฆ่าอินสแตนซ์ excel ทั้งหมดซึ่งถูกสร้างอินสแตนซ์หลังจากโค้ด excel-interoping เริ่ม
แต่! บางครั้งตัวเลือกทั้งหมดเหล่านี้ก็ไม่ช่วยหรือไม่เหมาะสม!
ตัวอย่างเช่นเมื่อวานฉันพบว่าในหนึ่งในฟังก์ชั่นของฉัน (ซึ่งทำงานได้กับ excel) Excel ยังคงทำงานต่อไปหลังจากที่ฟังก์ชั่นสิ้นสุดลง ฉันลองทุกอย่างแล้ว! ฉันตรวจสอบอย่างละเอียดฟังก์ชั่นทั้งหมด 10 ครั้งและเพิ่ม Marshal.FinalReleaseComObject () สำหรับทุกสิ่ง! ฉันยังมี GC.Collect () และ GC.WaitForPendingFinalizers () ฉันตรวจสอบกล่องข้อความที่ซ่อนอยู่ ฉันพยายามส่งข้อความ WM_CLOSE ไปที่หน้าต่างหลักของ Excel ฉันใช้งานฟังก์ชั่นของฉันใน AppDomain แยกต่างหากและยกเลิกการโหลดโดเมนนั้น ไม่มีอะไรช่วย! ตัวเลือกที่มีการปิดอินสแตนซ์ excel ทั้งหมดไม่เหมาะสมเพราะหากผู้ใช้เริ่มต้นอินสแตนซ์ Excel อื่นด้วยตนเองในระหว่างการดำเนินการฟังก์ชั่นของฉันซึ่งใช้งานกับ Excel ได้อินสแตนซ์นั้นจะถูกปิดโดยฟังก์ชันของฉัน ฉันพนันได้เลยว่าผู้ใช้จะไม่มีความสุข! ดังนั้นโดยสุจริตนี่เป็นตัวเลือกที่อ่อนแอ (ไม่มีผู้กระทำผิด)วิธีการแก้ปัญหา : ฆ่ากระบวนการ excel โดย hWnd ของหน้าต่างหลัก (มันเป็นทางออกแรก)
นี่คือรหัสง่าย ๆ :
[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();
}
ตามที่คุณเห็นฉันมีสองวิธีตามรูปแบบลองแยกวิเคราะห์ (ฉันคิดว่ามันเหมาะสมที่นี่): วิธีการหนึ่งไม่ส่งข้อยกเว้นหากกระบวนการไม่สามารถฆ่าได้ (ตัวอย่างเช่นกระบวนการไม่มีอีกต่อไป) และวิธีการอื่นจะส่งข้อยกเว้นหากกระบวนการไม่ได้ถูกฆ่า จุดอ่อนเดียวในรหัสนี้คือสิทธิ์ความปลอดภัย ในทางทฤษฎีผู้ใช้อาจไม่มีสิทธิ์ในการฆ่ากระบวนการ แต่ใน 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);
Voila! Excel ถูกยกเลิก! :)
ตกลงกลับไปที่วิธีแก้ปัญหาที่สองตามที่ฉันสัญญาไว้ในตอนต้นของการโพสต์
วิธีที่สองคือโทร GC.Collect () และ GC.WaitForPendingFinalizers () ใช่มันใช้งานได้จริง แต่คุณต้องระวังที่นี่ด้วย!
หลายคนพูดว่า (และฉันบอกว่า) การเรียก GC.Collect () ไม่ได้ช่วยอะไร แต่เหตุผลที่มันไม่ช่วยก็คือถ้ายังมีการอ้างอิงถึงวัตถุ COM! หนึ่งในเหตุผลที่นิยมมากที่สุดสำหรับ GC.Collect () ที่ไม่เป็นประโยชน์คือการเรียกใช้โครงการในโหมดดีบั๊ก ในวัตถุโหมดดีบักที่ไม่ได้อ้างอิงจริงอีกต่อไปจะไม่ถูกรวบรวมขยะจนกว่าจะสิ้นสุดของวิธีการ
ดังนั้นหากคุณลองใช้ GC.Collect () และ GC.WaitForPendingFinalizers () และไม่ช่วยลองทำสิ่งต่อไปนี้:
1) ลองเรียกใช้โครงการของคุณในโหมด Release และตรวจสอบว่า 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 จะปิด =)