서문 : 제 대답에는 두 가지 해결책이 포함되어 있으므로 읽을 때주의해야합니다.
Excel 인스턴스를 언로드하는 방법에 대한 여러 가지 방법과 조언이 있습니다.
Marshal.FinalReleaseComObject ()를 사용하여 모든 com 객체를 명시 적으로 해제 (암시 적으로 생성 된 com 객체를 잊지 않음). 생성 된 모든 com 개체를 해제하려면 여기에 언급 된 2 개의 점 규칙을 사용할 수 있습니다.
Excel interop 개체를 올바르게 정리하려면 어떻게해야합니까?
CLR 릴리스를 사용하지 않는 comobjects로 만들기 위해 GC.Collect () 및 GC.WaitForPendingFinalizers () 호출 * (실제로 작동합니다. 자세한 내용은 두 번째 솔루션 참조)
COM 서버 응용 프로그램을 확인하면 사용자가 응답 할 때까지 기다리는 메시지 상자가 표시 될 수 있습니다 (Excel이 닫히지 못할 지 확실하지 않지만 몇 번 들었습니다)
기본 Excel 창으로 WM_CLOSE 메시지 보내기
별도의 AppDomain에서 Excel과 함께 작동하는 기능을 실행합니다. 일부 사람들은 AppDomain이 언로드 될 때 Excel 인스턴스가 종료 될 것이라고 생각합니다.
엑셀 인터로 핑 코드가 시작된 후 인스턴스화 된 모든 엑셀 인스턴스를 종료합니다.
그러나! 때로는 이러한 모든 옵션이 도움이되지 않거나 적절하지 않을 수 있습니다!
예를 들어, 어제 Excel에서 작동하는 내 함수 중 하나에서 Excel이 함수가 끝난 후에도 계속 실행된다는 것을 알았습니다. 나는 모든 것을 시도했다! 나는 전체 기능을 10 번 철저히 점검하고 Marshal.FinalReleaseComObject ()를 모두 추가했습니다! 또한 GC.Collect () 및 GC.WaitForPendingFinalizers ()가있었습니다. 숨겨진 메시지 상자를 확인했습니다. 기본 Excel 창에 WM_CLOSE 메시지를 보내려고했습니다. 별도의 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 패턴에 따라 두 가지 방법을 제공했습니다 (여기서 적절하다고 생각합니다). 프로세스를 종료 할 수없는 경우 한 가지 방법으로 예외가 발생하지 않습니다 (예 : 프로세스가 더 이상 존재하지 않음) 프로세스가 종료되지 않은 경우 다른 메소드에서 예외가 발생합니다. 이 코드에서 유일한 약점은 보안 권한입니다. 이론적으로 사용자는 프로세스를 종료 할 수있는 권한이 없을 수 있지만 모든 경우의 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);
짜잔! 엑셀이 종료되었습니다! :)
글의 시작 부분에서 약속했듯이 두 번째 해결책으로 돌아가 봅시다.
두 번째 해결책은 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이 닫힙니다 =)