Excelの相互運用オブジェクトを適切にクリーンアップするにはどうすればよいですか?


747

私はC#(ApplicationClass)でExcel相互運用機能を使用しており、finally句に次のコードを配置しています。

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

この種の動作はしExcel.exeますが、Excelを閉じた後でも、プロセスはバックグラウンドにあります。アプリケーションが手動で閉じられたときにのみリリースされます。

私は何を間違っていますか、または相互運用オブジェクトが適切に破棄されることを保証する代替手段はありますか?


1
アプリケーションを閉じずにExcel.exeをシャットダウンしようとしていますか?よくわかりません。
ブライアント、

3
アンマネージ相互運用オブジェクトが適切に破棄されることを確認しようとしています。そのため、ユーザーがアプリから作成したExcelスプレッドシートを使い終わっても、Excelプロセスが滞ることはありません。
2008年

3
XML Excelファイルを作成して実行できる場合は、VSTOのアンマネージ
Jeremy Thompson

これはExcelにうまく変換されますか?
Coops、

2
(以下の回答のほかに)Microsoftからのこのサポート記事を参照してください。このサポート記事では、この問題の解決策が具体的に示されています。support.microsoft.com
kb

回答:


684

アプリケーションがCOMオブジェクトへの参照をまだ保持しているため、Excelは終了しません。

変数に割り当てずに、COMオブジェクトの少なくとも1つのメンバーを呼び出していると思います。

私にとっては、変数に割り当てずに直接使用したexcelApp.Worksheetsオブジェクトでした。

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

内部的にC#がWorksheets COMオブジェクトのラッパーを作成し、それが私のコードによって解放されないことを知りませんでした(私はそれを知らなかったため)、Excelがアンロードされなかった原因です。

このページで問題の解決策を見つけました。これには、C#でのCOMオブジェクトの使用に関する素晴らしいルールもあります。

COMオブジェクトでは2つのドットを使用しないでください。


したがって、この知識があれば、上記の正しい方法は次のとおりです。

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

死後の更新:

私と他の多くの開発者が遭遇した罠について説明しているので、すべての読者にHans Passantのこの回答を非常に注意深く読んでもらいたい。私が何年も前にこの回答を書いたとき、デバッガがガベージコレクタに与える影響については知りませんでしたが、間違った結論を導き出しました。私は歴史の都合上、私の回答を変更しないでおきますが、このリンクを読んで、「2つの点」の方法を使わないでください。.NETのガベージコレクションエクセルInteropのクリーンアップは、IDisposableを持つオブジェクト


16
次に、COMからExcelを使用しないことをお勧めします。Excel 2007形式は、Excelを開かなくてもゴージャスに使用できます。
user7116 2009

5
「2つのドット」の意味がわかりませんでした。説明していただけますか?
A9S6、2010年

22
つまり、comObject.Property.PropertiesPropertyというパターンを使用しないでください(2つのドットが表示されますか?)。代わりに、comObject.Propertyを変数に割り当て、その変数を使用して破棄します。上記のルールのより正式なバージョンはsthです。「comオブジェクトを使用する前に変数に割り当てる。これには、別のcomオブジェクトのプロパティであるcomオブジェクトが含まれます。」
VVS

5
@Nick:実際には、ガベージコレクターが実行するため、クリーンアップは必要ありません。あなたがしなければならない唯一のことは、GCがそれを知っているように、すべてのCOMオブジェクトをそれ自身の変数に割り当てることです。
VVS 2010年

12
ラッパー変数は.netフレームワークによって作成されるため、@ VSSはbollocksを実行し、GCはすべてをクリーンアップします。GCがそれをクリーンアップするのに永遠にかかるかもしれません。相互運用性が高い後にGC.Collectを呼び出すことは悪い考えではありません。
CodingBarfield 2011

280

実際にExcelアプリケーションオブジェクトを完全に解放できますが、注意が必要です。

アクセスするすべてのCOMオブジェクトの名前付き参照を維持し、それを介して明示的に解放するというアドバイスMarshal.FinalReleaseComObject()は理論的には正しいですが、残念ながら実際の管理は非常に困難です。どこかにずれて「2つのドット」を使用したり、for eachループやその他の同様の種類のコマンドを介してセルを反復処理したりすると、COMオブジェクトが参照されなくなり、ハングする危険性があります。この場合、コードで原因を見つける方法はありません。すべてのコードを目で確認して、うまくいけば原因を見つける必要があります。大規模なプロジェクトではほぼ不可能と思われるタスクです。

良いニュースは、実際に使用するすべてのCOMオブジェクトへの名前付き変数参照を維持する必要がないことです。代わりに、を呼び出しGC.Collect()GC.WaitForPendingFinalizers()から、参照を保持していないすべての(通常はマイナー)オブジェクトを解放し、名前付き変数参照を保持しているオブジェクトを明示的に解放します。

また、名前付き参照を重要度の高い逆の順序で解放する必要があります。最初に範囲オブジェクト、次にワークシート、ワークブック、最後にExcelアプリケーションオブジェクトです。

たとえば、という名前のRangeオブジェクト変数、という名前xlRngのワークシート変数、という名前xlSheetのWorkbook変数、xlBookおよびという名前のExcelアプリケーション変数があるとすると、xlAppクリーンアップコードは次のようになります。

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

.NETからCOMオブジェクトをクリーンアップするために目にするほとんどのコード例では、GC.Collect()およびGC.WaitForPendingFinalizers()呼び出しは次のように2回行われます。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

ただし、オブジェクトのグラフ全体をファイナライズキューで昇格させるファイナライザーを使用するVisual Studio Tools for Office(VSTO)を使用している場合を除き、これは必要ありません。このようなオブジェクトは、次のガベージコレクションまで解放されません。あなたはVSTOを使用していない場合は、あなたが呼び出すことができるはずですGC.Collect()し、GC.WaitForPendingFinalizers()一度だけ。

明示的に呼び出すことGC.Collect()はノーノーです(確かに2回行うのは非常に痛いように聞こえます)が、正直に言うとそれを回避する方法はありません。通常の操作では、参照を保持していない非表示オブジェクトを生成するため、を呼び出す以外の方法で解放することはできませんGC.Collect()

これは複雑なトピックですが、これですべてです。クリーンアップ手順のこのテンプレートを確立したら、ラッパーなどを必要とせずに、通常のコードを作成できます。:-)

これに関するチュートリアルがあります:

VB.Net / COM相互運用機能によるOfficeプログラムの自動化

これはVB.NET用に書かれていますが、それによって延期されることはありません。原理はC#を使用する場合とまったく同じです。


3
関連するディスカッションは、ExtremeVBTalk .NET Office Automationフォーラム(xtremevbtalk.com/showthread.php?t=303928)にあります。
Mike Rosenblum、

2
ここに記載されたように)process.killで(そして、他のすべてが失敗した場合には、(最後の手段として)使用することができます。stackoverflow.com/questions/51462/...
マイク・ローゼンブラム

1
リチャードがうまくいったことを嬉しく思います。::-)そして、ここでは単に「2点」を回避する問題を回避するには十分でない微妙な例であるstackoverflow.com/questions/4964663/...
マイク・ローゼンブラム

2010年10月7日のコメント日内の、このトピックに関するMicrosoftのMisha Shneersonからのこれに対する意見です。(blogs.msdn.com/b/csharpfaq/archive/2010/09/28/…
Mike Rosenblum

これが私たちが抱えている永続的な問題の解決に役立つことを願っています。finallyブロック内でガベージコレクションとリリースコールを実行しても安全ですか?
Brett Green、

213

序文:私の回答には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が閉じます=)


19
スレッドは...私はそれがより多くの時間をupvotedされていない唯一の理由だと思うこれは、すでに優秀な答えはこれまで以下のように表示されるように古いであることを悲しい
chiccodoro

15
私があなたの答えを最初に読んだとき、私はそれを巨大な汚物だと思ったと認めざるを得ません。これに約6時間取り組んだ後(すべてが解放され、二重ドットがないなど)、あなたの答えは天才だと思います。
マーク

3
これをありがとう。私がこれを発見する前の数日間、何があっても閉じないExcelと格闘していました。優秀な。
BBlake、

2
@nightcoder:素晴らしい答え、本当に詳細。デバッグモードに関するあなたのコメントは非常に真実であり、あなたがこれを指摘したことは重要です。多くの場合、リリースモードの同じコードで問題ありません。ただし、Process.Killは、オートメーションを使用する場合にのみ有効であり、プログラムがExcel内で実行されている場合(マネージCOMアドインなど)は無効です。
Mike Rosenblum、2011

2
@DanM:あなたのアプローチは100%正しく、決して失敗することはありません。すべての変数をメソッドに対してローカルに保つことにより、すべての参照は.NETコードから事実上到達できなくなります。つまり、finallyセクションがGC.Collect()を呼び出すと、すべてのCOMオブジェクトのファイナライザが確実に呼び出されます。次に、各ファイナライザは、ファイナライズされるオブジェクトでMarshal.FinalReleaseComObjectを呼び出します。したがって、あなたのアプローチはシンプルで間違いのないものです。これを使用する恐れはありません。(警告のみ:VSTOを使用している場合、私がそうしていることを疑っている場合は、GC.Collect()およびGC.WaitForPendingFinalizers TWICEを呼び出す必要があります。)
Mike Rosenblum

49

更新:C#コードを追加し、Windowsジョブにリンク

私はいつかこの問題を解決しようと費やしましたが、そのときXtremeVBTalkが最もアクティブで応答性がありました。これが私の元の投稿へのリンクですアプリケーションがクラッシュしても、Excelの相互運用プロセスを完全に終了します。以下は、投稿の概要と、この投稿にコピーされたコードです。

  • 相互運用プロセスを閉じるApplication.Quit()と、Process.Kill()ほとんどの部分は動作しますが、アプリケーションが壊滅的にクラッシュした場合に失敗します。つまり、アプリがクラッシュした場合でも、Excelプロセスは緩やかに実行されます。
  • 解決策は、Win32呼び出しを使用して、Windowsジョブオブジェクトを通じてプロセスのクリーンアップをOSに処理させることです。メインアプリケーションが停止すると、関連するプロセス(Excelなど)も終了します。

OSがクリーンアップの実際の作業を行っているため、これはクリーンなソリューションであることがわかりました。Excelプロセスを登録するだけです。

Windowsジョブコード

Win32 API呼び出しをラップして、相互運用プロセスを登録します。

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

コンストラクタコードに関する注意

  • コンストラクタでinfo.LimitFlags = 0x2000;が呼び出されます。0x2000JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE列挙値であり、この値はMSDNによって次のように定義されています。

ジョブの最後のハンドルが閉じられると、ジョブに関連付けられたすべてのプロセスが終了します。

プロセスID(PID)を取得するための追加のWin32 API呼び出し

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

コードを使用する

    Excel.Application app = new Excel.ApplicationClass();
    Job job = new Job();
    uint pid = 0;
    Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
    job.AddProcess(Process.GetProcessById((int)pid).Handle);

2
プロセス外のCOMサーバー(他のCOMクライアントにサービスを提供している可能性がある)を明示的に強制終了することは恐ろしい考えです。これに頼っているのは、COMプロトコルが壊れているためです。COMサーバーを通常のウィンドウアプリとして扱うべきではありません
MickyD

39

これは私が取り組んでいたプロジェクトでうまくいきました:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

使い終わったら、Excel COMオブジェクトへのすべての参照をnullに設定することが重要であることを学びました。これには、セル、シート、その他すべてが含まれます。


30

Excel名前空間にあるものはすべて解放する必要があります。限目

あなたがすることはできません:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

あなたはしている必要があります

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

オブジェクトの解放が続きます。


3
たとえば、xlRange.Interior.Colorをどのように使用するか。
2008年

内部は解放する必要があります(名前空間にあります)...一方、色は解放しません(System.Drawing.Color、iircが原因です)
MagicKat

2
実際の色は、.Net色ではなくExcel色です。あなたはロングを渡すだけです。また、ワークブックの定義。リリースする必要があります、ワークシート...それほどではありません。
匿名タイプ

それは真実ではありません、2ドットの悪い、1ドットの良いルールはナンセンスです。また、GC、ワークブック、ワークシート、範囲を解放する必要はありません。忘れてはならない唯一のことは、唯一のExcel.ApplicationオブジェクトでQuitを呼び出すことです。Quitを呼び出した後、Excel.Applicationオブジェクトをnullにして、GC.Collect();を呼び出します。GC.WaitForPendingFinalizers(); 二回。
ディートリッヒバウムガルテン

29

最初に-あなたは決して呼び出す必要がないMarshal.ReleaseComObject(...)か、Marshal.FinalReleaseComObject(...)Excelの相互運用を行うとき。これは紛らわしいアンチパターンですが、.NETからCOM参照を手動で解放する必要があることを示す、Microsoftを含むこれに関する情報はすべて正しくありません。事実は、.NETランタイムとガベージコレクターがCOM参照を正しく追跡およびクリーンアップすることです。あなたのコードにとって、これはあなたが一番上の `while(...)ループ全体を取り除くことができることを意味します。

次に、プロセスが終了したときにプロセス外のCOMオブジェクトへのCOM参照が確実にクリーンアップされるようにする場合(Excelプロセスが閉じるため)、ガベージコレクターが実行されるようにする必要があります。これは、GC.Collect()およびの呼び出しで正しく行いますGC.WaitForPendingFinalizers()。これを2回呼び出すことは安全であり、サイクルも確実にクリーンアップされることを保証します(これが必要かどうかはわかりませんが、これを示す例に感謝します)。

3番目に、デバッガーで実行している場合、ローカル参照はメソッドの最後まで人工的に保持されます(ローカル変数の検査が機能するようにするため)。したがって 、同じメソッドからのGC.Collect()ようにrng.Cells、呼び出しはオブジェクトのクリーニングには効果的ではありません。COM相互運用を行うコードをGCクリーンアップから個別のメソッドに分割する必要があります。(これは、@ nightcoderによってここに投稿された回答の一部から、私にとって重要な発見でした。)

したがって、一般的なパターンは次のようになります。

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

この問題については、MSDNとスタックオーバーフローに関する多くの投稿(特にこの質問!)を含め、多くの誤った情報と混乱があります。

最終的に私が詳細を調べて適切なアドバイスを理解することを確信したのは、ブログの投稿Marshal.ReleaseComObjectが危険見なされ、以前のテストを混乱させていた参照がデバッガーの下で存続している問題を見つけることでした。


5
このページのほとんどすべての回答が間違っています。彼らは働きますが、あまりにも多くの仕事です。「2ドット」ルールを無視します。GCに任せてください。.NET GURU Hans Passantによる補足証拠:stackoverflow.com/a/25135685/3852958
Alan Baljeu

1
Govert、それを行うための正しい方法を見つけるためのなんと救済か。ありがとうございました。
ディートリッヒバウムガルテン

18

スコープ外に出たときにMarshal.ReleaseComObjectを呼び出す必要があるCOMオブジェクトの正しい廃棄パターンを実装するのに役立つ便利な汎用テンプレートを見つけました。

使用法:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

テンプレート:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

参照:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/


3
うん、これはいいね。FinalReleaseCOMObjectが利用可能になったので、このコードは更新できると思います。
匿名タイプ

オブジェクトごとにusingブロックを使用するのはまだ面倒です。別の方法については、stackoverflow.com / questions / 2191489 /…を参照してください。
Henrik

16

この問題が5年間世界を悩ませているとは思えません。アプリケーションを作成した場合は、リンクを削除する前にアプリケーションをシャットダウンする必要があります。

objExcel = new Excel.Application();  
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

閉じるとき

objBook.Close(true, Type.Missing, Type.Missing); 
objExcel.Application.Quit();
objExcel.Quit(); 

Excelアプリケーションを新規作成すると、バックグラウンドでExcelプログラムが開きます。そのExcelプログラムは直接制御の一部ではないため、リンクを解放する前にそのExcelプログラムを終了するように命令する必要があります。したがって、リンクが解放されても開いたままになります!

みんな良いプログラミング~~


いいえ、特にCOMアプリケーションとのユーザーインタラクションを許可する場合は、そうしません(この場合、Excelでは、OPはユーザーインタラクションが意図されているかどうかを指定しませんが、シャットダウンについては言及していません)。たとえば、単一のファイルを閉じるときに、ユーザーがExcelを終了できるように、呼び出し元を解放する必要があります。
ダウンウィッチ

これは私にとって完璧に機能しました-objExcel.Quit()しかなかったときにクラッシュしましたが、事前にobjExcel.Application.Quit()も実行していたので、完全に閉じました。
mcmillab 2015年

13

一般的な開発者、あなたのソリューションはどれも私のために機能しなかったので、新しい トリック

まず、「私たちの目標は何ですか?」=>「タスクマネージャでのジョブの後にExcelオブジェクトを表示しない」

OK。挑戦して破壊しようとしますが、並行して実行されている他のインスタンスos Excelを破壊しないことを検討してください。

したがって、現在のプロセッサのリストを取得し、EXCELプロセスのPIDをフェッチすると、ジョブが完了すると、一意のPIDを持つ新しいゲストがプロセスリストに追加され、そのゲストだけが見つかり、破棄されます。

<Excelジョブ中の新しいExcelプロセスはすべて新しいものとして検出され、破棄されることに注意してください> <より良い解決策は、新しく作成されたExcelオブジェクトのPIDをキャプチャして破棄することです>

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

これで私の問題は解決しました。


1
....これは、ソリハンマーアプローチのビットですか?-これも私が使用しているものに似ています:(しかし変更する必要があります。このアプローチを使用する際の問題は、Excelを開いたときに、これが実行されたマシンで、左側のバーに次のような警告が表示されることです。 「Excelが間違って閉鎖されました」:。。非常にエレガントではない私は、このスレッドでは、以前の提案の一つが好まれると思い
whytheq

11

これは確かに複雑すぎるようです。私の経験から、Excelを適切に閉じるには、次の3つの重要な点があります。

1:作成したExcelアプリケーションへの参照が残っていないことを確認します(とにかく1つだけ持つ必要があります。 null)。

2:電話する GC.Collect()

3:ユーザーがプログラムを手動で閉じるかQuit、Excelオブジェクトを呼び出すことによって、Excelを閉じる必要があります。(Quitは、ユーザーがプログラムを閉じようとした場合と同様に機能し、Excelが表示されていなくても、変更が保存されていない場合は確認ダイアログを表示します。ユーザーが[キャンセル]を押すと、Excelは閉じられません。 )

1は2の前に発生する必要がありますが、3はいつでも発生する可能性があります。

これを実装する1つの方法は、相互運用Excelオブジェクトを独自のクラスでラップし、コンストラクターで相互運用インスタンスを作成し、Disposeを使用してIDisposableを実装することです。

if (!mDisposed) {
   mExcel = null;
   GC.Collect();
   mDisposed = true;
}

これにより、プログラムの側面からExcelがクリーンアップされます。Excelが(ユーザーが手動で、またはを呼び出してQuit)終了すると、プロセスは終了します。プログラムが既に閉じられている場合、プロセスはGC.Collect()呼び出し時に消えます。

(それがどれほど重要かはわかりませんが、GC.WaitForPendingFinalizers()呼び出しの後にGC.Collect()呼び出しが必要になる場合がありますが、Excelプロセスを取り除く必要は必ずしもありません。)

これは何年も問題なく機能しました。ただし、これが機能している間は、実際には正常に終了する必要があります。Excelがクリーンアップされる前にプログラムを中断すると(通常、プログラムのデバッグ中に「停止」を押すことにより)、excel.exeプロセスが累積されます。


1
この回答は機能し、受け入れられた回答よりもはるかに便利です。COMオブジェクトでの「2つのドット」の心配はありませんMarshal
andrew.cuthbert 2015

9

読み取り時に各オブジェクトに直接参照を作成した場合でも、Excelが閉じない理由に加えて、作成は 'For'ループです。

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next

もう1つの理由は、前の値を最初に解放せずに参照を再利用することです。
グリムフォート

ありがとうございました。私はまた、あなたの解決策が私のために働いた「for each」を使用していました。
Chad Braun-Duin 2011年

+1ありがとうございます。さらに、Excel範囲のオブジェクトがあり、オブジェクトの存続期間中に範囲を変更したい場合は、再割り当てする前にReleaseComObjectを実行する必要があることがわかりました。これにより、コードが少し乱雑になります。
AjV Jsy 2014年

9

私は伝統的にVVSの回答にあるアドバイスに従ってきました。ただし、この回答を最新のオプションで最新の状態に保つために、今後のプロジェクトではすべて「NetOffice」ライブラリを使用すると思います。

NetOfficeはOffice PIAの完全な代替品であり、完全にバージョンに依存しません。これは、.NETでMicrosoft Officeを操作するときに、このような頭痛の原因となることが多いクリーンアップを処理できるマネージCOMラッパーのコレクションです。

主な機能は次のとおりです。

  • ほとんどの場合、バージョンに依存しません(およびバージョンに依存する機能が文書化されています)
  • 依存関係なし
  • PIAなし
  • 登録なし
  • VSTOなし

私はこのプロジェクトとはまったく関係がありません。頭痛の激減を心から感謝しています。


2
これは本当に答えとしてマークされるべきです。NetOfficeはこの複雑さをすべて抽象化します。
C.アウグストプロイエテ

1
私はかなり長い間NetOfficeを使用していて、Excelアドインを作成してきましたが、完全に機能しました。考慮すべき唯一のことは、使用済みのオブジェクトを明示的に破棄しない場合、アプリケーションを終了したときにそれを実行することです(とにかくオブジェクトを追跡しているため)。NetOfficeとの経験則だから等のセル、範囲やシートなどのすべてのExcelオブジェクトとパターン「を使用して」使用することが常にある
スタースイワノフ

8

ここで受け入れられた答えは正しいですが、「2つのドット」の参照だけでなく、インデックスを介して取得されるオブジェクトも回避する必要があることに注意してください。また、プログラムが終了してこれらのオブジェクトをクリーンアップするまで待機する必要はありません。可能な場合は、オブジェクトが終了したらすぐにクリーンアップする関数を作成することをお勧めします。以下は、Styleオブジェクトのいくつかのプロパティを割り当てる関数を作成したものですxlStyleHeader

public Excel.Style xlStyleHeader = null;

private void CreateHeaderStyle()
{
    Excel.Styles xlStyles = null;
    Excel.Font xlFont = null;
    Excel.Interior xlInterior = null;
    Excel.Borders xlBorders = null;
    Excel.Border xlBorderBottom = null;

    try
    {
        xlStyles = xlWorkbook.Styles;
        xlStyleHeader = xlStyles.Add("Header", Type.Missing);

        // Text Format
        xlStyleHeader.NumberFormat = "@";

        // Bold
        xlFont = xlStyleHeader.Font;
        xlFont.Bold = true;

        // Light Gray Cell Color
        xlInterior = xlStyleHeader.Interior;
        xlInterior.Color = 12632256;

        // Medium Bottom border
        xlBorders = xlStyleHeader.Borders;
        xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
        xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        Release(xlBorderBottom);
        Release(xlBorders);
        Release(xlInterior);
        Release(xlFont);
        Release(xlStyles);
    }
}

private void Release(object obj)
{
    // Errors are ignored per Microsoft's suggestion for this type of function:
    // http://support.microsoft.com/default.aspx/kb/317109
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
    }
    catch { } 
}

設定しなければならないことに注意してください xlBorders[Excel.XlBordersIndex.xlEdgeBottom]クリーンアップするために変数する必要があったことに注意してください(解放する必要のない列挙を参照する2つのドットのためではなく、参照しているオブジェクトが実際にはBorderオブジェクトだからです)解放する必要があります)。

この種のことは、標準的なアプリケーションでは実際には必要ありません。標準のアプリケーションは、後片付けを行うのに優れていますが、ASP.NETアプリケーションでは、ガベージコレクターを呼び出す頻度に関係なく、これらの1つでも見逃した場合、Excelはサーバー上でまだ実行されています。

このコードを書くときにタスクマネージャーを監視しながら、詳細と多くのテスト実行に多くの注意を払う必要がありますが、そうすることで、コードのページを必死に検索して見逃したインスタンスを見つける手間が省けます。これは、オブジェクトがループするたびに同じ変数名を使用する場合でも、オブジェクトの各インスタンスを解放する必要があるループで作業する場合に特に重要です。


リリース内で、ヌル(ジョーの答え)を確認します。これにより、不要なnull例外が回避されます。たくさんのメソッドをテストしました。これは、Excelを効果的にリリースする唯一の方法です。
Gerhard Powell

8

試した後

  1. COMオブジェクトを逆の順序で解放する
  2. 追加GC.Collect()してGC.WaitForPendingFinalizers()最後に 2回
  3. 2つ以下のドット
  4. ブックを閉じてアプリケーションを終了
  5. リリースモードで実行する

私にとって有効な最終的な解決策は、1つのセットを移動することです

GC.Collect();
GC.WaitForPendingFinalizers();

次のように、関数の最後にラッパーに追加しました。

private void FunctionWrapper(string sourcePath, string targetPath)
{
    try
    {
        FunctionThatCallsExcel(sourcePath, targetPath);
    }
    finally
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

ComObjectsを無効にする必要はなく、Quit()-Close()とFinalReleaseComObjectのみを無効にする必要があることがわかりました。これが機能するために私の最後に欠けていたすべてがこれであるとは信じられません。すごい!
キャロル

7

私はこれに正確に従いました...しかし、私はまだ1000回に1回問題に遭遇しました。理由は誰にもわかりません。ハンマーを引き出す時間...

Excel Applicationクラスがインスタンス化された直後に、作成されたばかりのExcelプロセスを取得します。

excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();

次に、上記のCOMクリーンアップをすべて実行したら、そのプロセスが実行されていないことを確認します。それがまだ実行されている場合は、殺してください!

if (!process.HasExited)
   process.Kill();

7

Excel°º¤ø„¸Excel procを撃ち、バブルガムを噛む¸„ø¤º°¨

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}

6

Excelは、実行しているカルチャにも非常に敏感であることを認識する必要があります。

Excel関数を呼び出す前に、カルチャをEN-USに設定する必要がある場合があります。これはすべての機能に適用されるわけではありませんが、一部の機能には適用されます。

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

これは、VSTOを使用している場合にも当てはまります。

詳細:http : //support.microsoft.com/default.aspx?scid=kb ; en-us ; Q320369


6

「COMオブジェクトで2つのドットを使用しない」は、COM参照のリークを回避するための優れた経験則ですが、Excel PIAは、一目でわかるよりも多くの方法でリークを引き起こす可能性があります。

これらの方法の1つは、ExcelオブジェクトモデルのCOMオブジェクトのいずれかによって公開されるイベントをサブスクライブすることです。

たとえば、ApplicationクラスのWorkbookOpenイベントをサブスクライブします。

COMイベントに関する理論

COMクラスは、コールバックインターフェイスを通じてイベントのグループを公開します。イベントをサブスクライブするために、クライアントコードはコールバックインターフェイスを実装するオブジェクトを登録するだけでよく、COMクラスは特定のイベントに応答してそのメソッドを呼び出します。コールバックインターフェイスはCOMインターフェイスであるため、イベントハンドラーのいずれかについて(パラメーターとして)受け取るCOMオブジェクトの参照カウントをデクリメントすることは、実装するオブジェクトの義務です。

Excel PIAがCOMイベントを公開する方法

Excel PIAは、Excel ApplicationクラスのCOMイベントを従来の.NETイベントとして公開します。クライアントコードが.NETイベント( 'a'に重点を置く)をサブスクライブするときは常に、PIAはコールバックインターフェイスを実装するクラスのインスタンスを作成しそれをExcelに登録します。

したがって、.NETコードからのさまざまなサブスクリプション要求に応じて、いくつかのコールバックオブジェクトがExcelに登録されます。イベントサブスクリプションごとに1つのコールバックオブジェクト。

イベント処理用のコールバックインターフェイスは、PIAがすべての.NETイベントサブスクリプション要求のすべてのインターフェイスイベントにサブスクライブする必要があることを意味します。選ぶことはできません。イベントコールバックを受信すると、コールバックオブジェクトは、関連付けられている.NETイベントハンドラーが現在のイベントに関心があるかどうかを確認し、ハンドラーを呼び出すか、コールバックをサイレントに無視します。

COMインスタンス参照カウントへの影響

これらすべてのコールバックオブジェクトは、コールバックメソッドのいずれかについて(パラメーターとして)受け取るCOMオブジェクトの参照カウントをデクリメントしません(暗黙的に無視されるものでも)。CLRのみに依存している、COMオブジェクトを解放するためにガベージコレクターます。

GCの実行は非決定的であるため、Excelプロセスが必要以上に長く保留され、「メモリリーク」の印象を与える可能性があります。

解決

現在の唯一の解決策は、COMクラスのPIAのイベントプロバイダーを回避し、COMオブジェクトを確定的に解放する独自のイベントプロバイダーを作成することです。

Applicationクラスの場合、これはAppEventsインターフェイスを実装し、IConnectionPointContainerインターフェイスを使用してExcelに実装を登録することで実行できます。Applicationクラス(さらに言えば、コールバックメカニズムを使用してイベントを公開するすべてのCOMオブジェクト)は、IConnectionPointContainerインターフェイスを実装します。


4

他の人が指摘したように、このKB記事で説明されているように、使用するすべてのExcelオブジェクトに対して明示的な参照を作成し、その参照に対してMarshal.ReleaseComObjectを呼び出す必要があります。また、try / finallyを使用して、例外がスローされた場合でも、ReleaseComObjectが常に呼び出されるようにする必要もあります。つまり、代わりに:

Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet

あなたは次のようなことをする必要があります:

Worksheets sheets = null;
Worksheet sheet = null
try
{ 
    sheets = excelApp.Worksheets;
    sheet = sheets(1);
    ...
}
finally
{
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
}

また、Excelを閉じる場合は、Applicationオブジェクトを解放する前にApplication.Quitを呼び出す必要があります。

ご覧のとおり、中程度の複雑さでも何かを実行しようとすると、これはすぐに非常に扱いにくくなります。私は、Excelオブジェクトモデルのいくつかの単純な操作(ワークブックを開く、Rangeに書き込む、ワークブックを保存/閉じるなど)をラップするシンプルなラッパークラスを使用して.NETアプリケーションの開発に成功しました。ラッパークラスはIDisposableを実装し、使用するすべてのオブジェクトにMarshal.ReleaseComObjectを慎重に実装します。Excelオブジェクトをアプリの残りの部分に公開することはありません。

しかし、このアプローチは、より複雑な要件にうまく対応できません。

これは.NET COM Interopの大きな欠点です。より複雑なシナリオについては、VB6またはその他のアンマネージ言語でActiveX DLLを作成することを真剣に検討します。これにより、OfficeなどのアウトプロセスCOMオブジェクトとのすべての対話を委任できます。その後、.NETアプリケーションからこのActiveX DLLを参照できます。この1つの参照を解放するだけでよいので、状況ははるかに簡単になります。


3

上記のすべてが機能しない場合は、Excelにシートを閉じる時間を与えてみてください。

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);

2
私は任意の待機が嫌いです。しかし、+ 1は、ワークブックが閉じるのを待つ必要があることについて正しいからです。別の方法は、ループでワークブックコレクションをポーリングし、ループタイムアウトとして任意の待機を使用することです。
dFlat 2011

3

Excelに関連するすべてのオブジェクトを解放してください。

私はいくつかの方法を試して数時間を費やしました。すべてが素晴らしいアイデアですが、私はついに私の間違いを発見しました。すべてのオブジェクトを解放しないと、上記の方法のどれも私の場合のようにあなたを助けることができません。範囲1を含むすべてのオブジェクトを解放してください!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

オプションはここにあります


とても良い点!私はOffice 2007を使用しているため、私の代わりにいくつかのクリーンアップが行われたと思います。私はアドバイスをさらに使いましたが、ここで提案したように変数を保存していません。EXCEL.EXEは終了しますが、幸運であり、さらに問題が発生した場合は、コードのこの部分を確認します=)
Coops

3

COMオブジェクトの解放に関する優れた記事は、2.5 COMオブジェクトの解放です。(MSDN)です。

私が提唱する方法は、Excel.Interop参照が非ローカル変数である場合はそれらをnullにしてから呼び出しGC.Collect()GC.WaitForPendingFinalizers() 2回です。ローカルスコープの相互運用変数は自動的に処理されます。

これにより、すべての COMオブジェクトの名前付き参照を保持する必要がなくなります。

これは記事から取った例です:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

これらの単語は記事から直接です:

ほとんどすべての状況で、RCW参照をnullにしてガベージコレクションを強制すると、適切にクリーンアップされます。GC.WaitForPendingFinalizersも呼び出す場合、ガベージコレクションは、可能な限り確定的になります。つまり、オブジェクトがいつクリーンアップされたか、2回目のWaitForPendingFinalizersの呼び出しから戻ったときに、かなり正確になります。別の方法として、Marshal.ReleaseComObjectを使用できます。ただし、このメソッドを使用する必要がほとんどないことに注意してください。


2

二点法はうまくいきませんでした。私の場合、次のようにリソースをクリーンアップするメソッドを作成しました。

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}

2

私の解決策

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}

2

Word / Excel相互運用アプリケーションの使用には十分注意する必要があります。すべてのソリューションを試した後も、サーバー上で(WinWord)プロセスの多くを開いたままにしました(ユーザー数が2000人以上)。

問題に何時間も取り組んだ後、2つ以上のドキュメントを開くと、 Word.ApplicationClass.Document.Open()、異なるスレッドで同時にしていると、IISワーカープロセス(w3wp.exe)がクラッシュし、すべてのWinWordプロセスが開いたままになること。

したがって、この問題に対する絶対的な解決策はないと思いますが、Office Open XML開発などの他の方法に切り替えます


1

受け入れられた答えは私にとってはうまくいきませんでした。デストラクタ内の次のコードがその仕事をしました。

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}


1

私は現在Officeオートメーションに取り組んでおり、毎回私のために機能するこのソリューションに偶然出会いました。これは単純で、プロセスを強制終了する必要はありません。

現在アクティブなプロセスを単にループし、開いているExcelプロセスに「アクセス」するだけで、Excelの浮遊しているインスタンスが削除されるようです。以下のコードは、名前が「Excel」であるプロセスを単にチェックしてから、プロセスのMainWindowTitleプロパティを文字列に書き込みます。このプロセスとの「相互作用」により、WindowsはExcelのフリーズされたインスタンスを追いつき、中止するようになります。

アンロードイベントを発生させるため、開発中のアドインが終了する直前に以下のメソッドを実行します。毎回、ハングしているExcelのインスタンスをすべて削除します。正直なところ、これが機能する理由は完全にはわかりませんが、私にとってはうまく機能し、二重ドット、Marshal.ReleaseComObject、またはプロセスの強制終了を心配することなく、Excelアプリケーションの最後に配置できます。なぜこれが効果的であるかについて、私はどんな提案にも非常に興味があります。

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}

1

その一部はフレームワークがOfficeアプリケーションを処理する方法に過ぎないと思いますが、私は間違っている可能性があります。一部のアプリケーションは、プロセスをすぐにクリーンアップする場合と、アプリケーションが閉じるまで待機する場合があります。一般に、私は詳細に注意を払うのをやめ、1日の終わりに余分なプロセスが流れていないことを確認します。

また、私は単純化しすぎているかもしれませんが、あなたはただ...

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

先ほど言ったように、Excelプロセスがいつ表示または非表示になるかについては細心の注意を払う傾向はありませんが、通常はそれでうまくいきます。また、Excelのプロセスを最小限の時間以外は維持したくないのですが、おそらくそれについて偏執的です。


1

一部のユーザーはおそらく既に記述しているので、Excel(オブジェクト)を閉じる方法は重要ではありません。開く方法も重要ですや、プロジェクトの種類によって。

WPFアプリケーションでは、基本的に同じコードが問題なく動作します。

同じExcelファイルが異なるパラメーター値について複数回処理されているプロジェクトがあります。たとえば、ジェネリックリスト内の値に基づいて解析します。

私はすべてのExcel関連関数を基本クラスに入れ、パーサーをサブクラスに入れました(パーサーごとに共通のExcel関数を使用しています)。ジェネリックリストのアイテムごとにExcelを開いたり閉じたりしたくなかったので、基本クラスで一度だけ開いて、サブクラスで閉じました。コードをデスクトップアプリケーションに移動するときに問題が発生しました。上記の解決策の多くを試しました。GC.Collect()すでに提案されているように、2回は実装済みでした。

次に、Excelを開くためのコードをサブクラスに移動することにしました。1回だけ開くのではなく、新しいオブジェクト(基本クラス)を作成し、すべてのアイテムに対してExcelを開いて最後に閉じます。パフォーマンスは多少低下しますが、いくつかのテストに基づいて、Excelプロセスは問題なく(デバッグモードで)終了しているため、一時ファイルも削除されます。テストを続行し、更新がある場合はさらに書きます。

つまり、特に多くのクラスなどがある場合は、初期化コードも確認する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.