序文:私の回答には2つの解決策が含まれているので、読むときは注意してください。
Excelインスタンスをアンロードする方法には、次のようなさまざまな方法とアドバイスがあります。
Marshal.FinalReleaseComObject()を使用して、EVERY comオブジェクトを明示的に解放します(暗黙的に作成されたcomオブジェクトを忘れないでください)。作成されたすべてのcomオブジェクトを解放するには、ここに記載されている2つのドットのルールを使用
できます。Excelの相互運用オブジェクトを適切にクリーンアップするにはどうすればよいですか?
GC.Collect()およびGC.WaitForPendingFinalizers()を呼び出して、CLRが未使用のcom-objectsを解放するようにします*(実際には、機能します。詳細については、2番目のソリューションを参照してください)
com-server-applicationにユーザーが応答するのを待つメッセージボックスが表示されるかどうかを確認します(Excelが閉じられない可能性があるかどうかはわかりませんが、何度か聞いています)。
WM_CLOSEメッセージをExcelのメインウィンドウに送信する
Excelで機能する関数を別のAppDomainで実行する。AppDomainがアンロードされると、Excelインスタンスがシャットダウンされると信じている人もいます。
Excel相互運用コードの開始後にインスタンス化されたすべてのExcelインスタンスを強制終了します。
だが!これらすべてのオプションが役に立たないか、適切でない場合があります。
たとえば、昨日、Excelで機能する関数の1つで、関数が終了した後もExcelが実行し続けることがわかりました。全部試しました!関数全体を10回徹底的にチェックし、すべてにMarshal.FinalReleaseComObject()を追加しました。GC.Collect()とGC.WaitForPendingFinalizers()も持っていました。非表示のメッセージボックスを確認しました。メインのExcelウィンドウにWM_CLOSEメッセージを送信しようとしました。別のAppDomainで関数を実行し、そのドメインをアンロードしました。何も役に立たなかった!すべてのExcelインスタンスを閉じるオプションは不適切です。ユーザーが別のExcelインスタンスを手動で開始すると、Excelでも機能する関数の実行中に、そのインスタンスも関数によって閉じられるためです。ユーザーは満足できないに違いない!だから、正直なところ、これは不十分なオプションです(攻撃者はいません)。solution:メインウィンドウの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パターンに従って、2つのメソッドを提供しました(ここでは適切だと思います)。1つのメソッドは、プロセスを強制終了できなかった場合に例外をスローしません(たとえば、プロセスがもう存在しないなど)。 、およびプロセスが強制終了されなかった場合、別のメソッドが例外をスローします。このコードの唯一の弱点は、セキュリティ権限です。理論的には、ユーザーにはプロセスを強制終了する権限がない場合がありますが、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が終了しました!:)
では、投稿の冒頭で約束したように、2番目のソリューションに戻りましょう。
2番目の解決策は、GC.Collect()およびGC.WaitForPendingFinalizers()を呼び出すことです。はい、実際に機能しますが、ここで注意する必要があります!
多くの人々は、GC.Collect()を呼び出しても役に立たないと言っています(私も言っています)。しかし、それでも役に立たない理由は、COMオブジェクトへの参照がまだある場合です。GC.Collect()が役に立たない最も一般的な理由の1つは、プロジェクトをデバッグモードで実行していることです。デバッグモードでは、実際には参照されなくなったオブジェクトは、メソッドが終了するまでガベージコレクションされません。
したがって、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が閉じます=)