Excel Interopに問題があります。
インスタンスを再起動しても、Excel.exeが閉じません。
ここに私のコードがあります:
using xl = Microsoft.Office.Interop.Excel;
xl.Application Excel = new xl.Application();
Excel.Visible = true;
Excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
//typeExcel become a string of the document name
string typeExcel = wordFile.ToString();
xl.Workbook workbook = Excel.Workbooks.Open(typeExcel,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing);
object outputFileName = null;
if (wordFile.Contains(".xls"))
{
outputFileName = wordFile.Replace(".xls", ".pdf");
}
else if (wordFile.Contains(".csv"))
{
outputFileName = wordFile.Replace(".csv", ".pdf");
}
workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName,
XlFixedFormatQuality.xlQualityStandard, oMissing,
oMissing, oMissing, oMissing, oMissing, oMissing);
object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);
Marshal.ReleaseComObject(workbook);
workbook = null;
}
私はそれを見た、Marshal.RealeaseComObject
動作するはずですが、何もありません。どうすれば修正できますか?
ありがとうございました。
単純なルール:次のような二重ドット呼び出し式の使用を避けます。
var workbook = Excel.Workbooks.Open(/*params*/)
...この方法で [〜#〜] rcw [〜#〜]workbook
だけでなくWorkbooks
のオブジェクトを作成するため、それも解放します(オブジェクトへの参照が維持されていない場合は不可能です)。
したがって、正しい方法は次のとおりです。
var workbooks = Excel.Workbooks;
var workbook = workbooks.Open(/*params*/)
//business logic here
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(Excel);
私があなたと同じ問題を抱えていたので、ここに私が書いたコードの断片があります。基本的に、ブックを閉じてアプリケーションを終了し、COMアプリケーション(Excelアプリケーションオブジェクトだけでなく)をすべて解放する必要があります。最後に、適切な測定のためにガベージコレクターを呼び出します。
/// <summary>
/// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
/// </summary>
public void Dispose()
{
// Cleanup
xWorkbook.Close(false);
xApp.Quit();
// Manual disposal because of COM
while (Marshal.ReleaseComObject(xApp) != 0) { }
while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
while (Marshal.ReleaseComObject(xCharts) != 0) { }
while (Marshal.ReleaseComObject(xMyChart) != 0) { }
while (Marshal.ReleaseComObject(xGraph) != 0) { }
while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
while (Marshal.ReleaseComObject(xSeries) != 0) { }
xApp = null;
xWorkbook = null;
xWorksheets = null;
xWorksheet = null;
xCharts = null;
xMyChart = null;
xGraph = null;
xSeriesColl = null;
xSeries = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
ルール-1ドット以上使用しない
-1つのドット
var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);
-2つ以上のドット
(Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);
-例
using Microsoft.Office.Interop.Excel;
Application xls = null;
Workbooks workBooks = null;
Workbook workBook = null;
Sheets sheets = null;
Worksheet workSheet1 = null;
Worksheet workSheet2 = null;
workBooks = xls.Workbooks;
workBook = workBooks.Open(workSpaceFile);
sheets = workBook.Worksheets;
workSheet1 = (Worksheet)sheets[1];
// removing from Memory
if (xls != null)
{
foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
{
ReleaseObject(sheet);
}
ReleaseObject(sheets);
workBook.Close();
ReleaseObject(workBook);
ReleaseObject(workBooks);
xls.Application.Quit(); // THIS IS WHAT IS CAUSES Excel TO CLOSE
xls.Quit();
ReleaseObject(xls);
sheets = null;
workBook = null;
workBooks = null;
xls = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
呼び出しが次のようなものかどうかを推測する必要があるため、すべての参照を削除するのは難しいです。
_var workbook = Excel.Workbooks.Open("")
_
参照を持たないWorkbooks
のインスタンスを作成します。
次のような参照でも:
_targetRange.Columns.AutoFit()
_
知らないうちに.Columns()
のインスタンスを作成し、適切にリリースされません。
最終的に、すべてのオブジェクトを逆の順序で破棄できるオブジェクト参照のリストを保持するクラスを作成しました。
クラスには、オブジェクトのリストと、オブジェクト自体を返すExcel相互運用機能を使用するときに参照するすべてのAdd()
関数があります。
_ public List<Object> _interopObjectList = new List<Object>();
public Excel.Application add(Excel.Application obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Range add(Excel.Range obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbook add(Excel.Workbook obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheet add(Excel.Worksheet obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheets add(Excel.Worksheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Sheets add(Excel.Sheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbooks add(Excel.Workbooks obj)
{
_interopObjectList.Add(obj);
return obj;
}
_
次に、オブジェクトの登録を解除するために、次のコードを使用しました。
_ //Release all registered interop objects in reverse order
public void unregister()
{
//Loop object list in reverse order and release Office object
for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
{ ReleaseComObject(_interopObjectList[i]); }
//Clear object list
_interopObjectList.Clear();
}
/// <summary>
/// Release a com interop object
/// </summary>
/// <param name="obj"></param>
public static void ReleaseComObject(object obj)
{
if (obj != null && InteropServices.Marshal.IsComObject(obj))
try
{
InteropServices.Marshal.FinalReleaseComObject(obj);
}
catch { }
finally
{
obj = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
_
次に、原則として、クラスを作成し、次のような参照をキャプチャします。
_//Create helper class
xlsHandler xlObj = new xlsHandler();
..
//Sample - Capture reference to Excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());
..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns()
xlObj.add(_targetCell.Columns).AutoFit();
..
//Release all objects collected by helper class
xlObj.unregister();
_
おそらく素晴らしいコードではありませんが、何か有用なものを刺激するかもしれません。
あなたのコードには次のものがあります:
Excel.Workbooks.Open(...)
Excel.Workbooks
はCOMオブジェクトを作成しています。次に、そのCOMオブジェクトからOpen
関数を呼び出しています。ただし、終了時にCOMオブジェクトを解放するわけではありません。
これは、COMオブジェクトを扱う際の一般的な問題です。基本的に、式に複数のドットを含めないでください。完了したら、COMオブジェクトをクリーンアップする必要があります。
このトピックは単純に答えを完全に探求するには大きすぎますが、Jake Ginnivanの主題に関する記事は非常に役立つと思います。 VSTOおよびCOM Interop
これらすべてのReleaseComObject呼び出しに飽きたら、この質問が役に立つかもしれません:
C#、2012年版でExcel相互運用オブジェクトを適切にクリーンアップする方法
他の回答で述べたように、2つのドットを使用すると、Marshal.FinalReleaseComObject
で閉じることができない隠し参照が作成されます。ソリューションを共有したかったので、Marshal.FinalReleaseComObject
を覚える必要がなくなりました。見逃すのは本当に簡単で、犯人を見つけるのは大変です。
generic IDisposable wrapper classを使用します。これは任意のCOMオブジェクトで使用できます。それは魅力のように機能し、すべてをきれいに保ちます。プライベートフィールド(たとえば、this.worksheet
)を再利用することもできます。また、IDisposable(Disposeメソッドはfinally
として実行されます)の性質により、何かがエラーをスローしたときにオブジェクトを自動解放します。
using Microsoft.Office.Interop.Excel;
public class ExcelService
{
private _Worksheet worksheet;
private class ComObject<TType> : IDisposable
{
public TType Instance { get; set; }
public ComObject(TType instance)
{
this.Instance = instance;
}
public void Dispose()
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
}
}
public void CreateExcelFile(string fullFilePath)
{
using (var comApplication = new ComObject<Application>(new Application()))
{
var excelInstance = comApplication.Instance;
excelInstance.Visible = false;
excelInstance.DisplayAlerts = false;
try
{
using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
{
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Action";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Status";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "ItemPrices";
this.worksheet.Activate();
using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
{
comRange.Instance.Select();
comWindow.Instance.FreezePanes = true;
}
}
if (this.fullFilePath != null)
{
var currentWorkbook = (workbook.Instance as _Workbook);
currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
currentWorkbook.Close(false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
throw;
}
finally
{
// Close Excel instance
excelInstance.Quit();
}
}
}
}
desperateの場合。 何をするのか理解していない限り、このアプローチは使用しないでください:
foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("Excel"))
{
proc.Kill();
}
注:これにより、「Excel」という名前のすべてのプロセスが強制終了されます。
コード内のすべてのCOMオブジェクトを閉じたにもかかわらず、頑固なExcel.exeプロセスがまだぶら下がっていたため、それを行う必要がありました。もちろん、これは決して最良の解決策ではありません。
これを複雑にしすぎないでください!!単純なメソッドを作成し、そのメソッドを次のように呼び出します。
// to kill the EXCELsheet file process from process Bar
private void KillSpecificExcelFileProcess() {
foreach (Process clsProcess in Process.GetProcesses())
if (clsProcess.ProcessName.Equals("Excel")) //Process Excel?
clsProcess.Kill();
}
または、 here の説明に従ってExcelプロセスを強制終了することもできます。
まず、インポートSendMessage関数:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
次に、WM_CLOSEメッセージをメインウィンドウに送信します。
SendMessage((IntPtr)Excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
@Denis Molodtsovは、「Excel」という名前のすべてのプロセスを強制終了することを提案しました。それはトラブルを求めているようです。 COM相互運用でNiceを再生することにより、Excel.quit()の呼び出し後にプロセスを停止させる方法を説明する多くの回答が既にあります。これが機能するのであれば、これが最適です。
@Kevin Vuilleumierは、WM_CLOSEをExcelウィンドウに送信することをお勧めしました。これをテストする予定です。
何らかの理由でExcelアプリオブジェクトのExcelプロセスを強制終了する必要がある場合は、次のようなものを使用して具体的にターゲットにできます。
using System.Diagnostics;
using System.Runtime.InteropServices;
// . . .
[DllImport("user32.dll", SetLastError=true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
// . . .
uint excelAppPid;
uint tid = GetWindowThreadProcessId(Excel.Hwnd, out excelAppPid);
if (tid)
{
Process excelAppProc = Process.GetProcessById($excelPid)
if (excelAppProc)
{
excelAppProc.Kill()
}
}
C#で完全にテストする時間はありませんが、Powershellでクイックテストを実行しました。そこでは、Excelが終了しないという問題があり、このアプローチは機能します。
とても簡単です。 Excel AppオブジェクトのHwndプロパティは、Excelプロセスの非表示ウィンドウハンドルです。 Excel.HwndをGetWindowThreadProcessIdに渡して、プロセスIDを取得します。それを使用してプロセスを開き、最後にKill()を呼び出します。
少なくとも、適切なプロセスを削除していると確信しています。まあ、かなり確か。 Excelプロセスが既に正常に終了している場合、そのプロセスIDは新しいプロセスで再利用できます。この可能性を制限するには、Excel.quit()を呼び出してから強制終了するまで待機しないことが重要です。
私は同じ問題を抱えていましたが、殺すことなく問題を解決できます.Microsoft.Office.Interop.Excelクラスから使用したインターフェイスを閉じることを常に忘れているので、ここにコードスニペットがあり、オブジェクトをクリアした構造と方法に従いますコード内のSheetsインターフェースに注目してください。これは、アプリケーション、Workbook、workbooks、range、sheetを頻繁に閉じる主な原因ですが、Sheetsオブジェクトまたは使用済みインターフェースを忘れたり、知らないうちにリリースしないでください。
Microsoft.Office.Interop.Excel.Application app = null;
Microsoft.Office.Interop.Excel.Workbooks books = null;
Workbook book = null;
Sheets sheets = null;
Worksheet sheet = null;
Range range = null;
try
{
app = new Microsoft.Office.Interop.Excel.Application();
books = app.Workbooks;
book = books.Add();
sheets = book.Sheets;
sheet = sheets.Add();
range = sheet.Range["A1"];
range.Value = "Lorem Ipsum";
book.SaveAs(@"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
book.Close();
app.Quit();
}
finally
{
if (range != null) Marshal.ReleaseComObject(range);
if (sheet != null) Marshal.ReleaseComObject(sheet);
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (book != null) Marshal.ReleaseComObject(book);
if (books != null) Marshal.ReleaseComObject(books);
if (app != null) Marshal.ReleaseComObject(app);
}