web-dev-qa-db-ja.com

Excel相互運用機能オブジェクトを正しいクリーンアップする方法を教えてください。

私はC#(ApplicationClass)でExcelの相互運用機能を使用していて、私のfinally節に次のコードを追加しました。

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

このような作業ですが、Excelを閉じた後もExcel.exeプロセスはバックグラウンドで実行されています。私のアプリケーションが手動で閉じられると、それは解放されます。

私は何を間違っていますか、または相互運用オブジェクトが適切に処分されるようにするための代替手段はありますか?

710
HAdes

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

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

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

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

私は、C#がWorksheetsCOMオブジェクトのラッパーを内部で作成したことを知りませんでした。それ))そしてExcelがアンロードされなかった原因でした。

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

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


したがって、この知識を使用して上記を行う正しい方法は次のとおりです。

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

POST MORTEM UPDATE:

私と他の多くの開発者が偶然出会ったtrapについて説明しているので、すべての読者にこのHans Passantの答えを注意深く読んでもらいたい。数年前にこの回答を書いたとき、デバッガがガベージコレクタに与える影響については知りませんでしたが、間違った結論を導きました。歴史のために答えは変更しませんが、このリンクを読んで、してはいけない「2つのドット」の道を進んでください: .NETのガベージコレクションについて および IDisposableを使用したExcel Interopオブジェクトのクリーンアップ

668
VVS

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

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

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

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

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

// 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)を使用している場合を除き、これは必要ありません。そのようなオブジェクトは、nextガベージコレクションまで解放されません。ただし、VSTOを使用していない場合は、GC.Collect()GC.WaitForPendingFinalizers()を一度だけ呼び出すことができます。

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

これは複雑なトピックですが、これですべてです。クリーンアップ手順用にこのテンプレートを確立すると、ラッパーなどを必要とせずに通常どおりコーディングできます:-)

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

VB.Net/COM Interopを使用したOfficeプログラムの自動化

これはVB.NET向けに書かれていますが、それで先送りしないでください。原則はC#を使用する場合とまったく同じです。

272
Mike Rosenblum

序文:私の答えには2つの解決策が含まれているので、読むときは注意して、何も見逃さないでください

次のように、Excelインスタンスをアンロードする方法については、さまざまな方法やアドバイスがあります。

  • Marshal.FinalReleaseComObject()を使用してEVERY comオブジェクトを明示的に解放します(暗黙的に作成されたcom-objectsを忘れないでください)。作成されたすべてのcomオブジェクトを解放するには、ここに記載されている2ドットの規則を使用できます。
    Excelの相互運用機能オブジェクトを正しくクリーンアップする方法を教えてください。

  • 未使用のcom-object *を解放するためにGC.Collect()とGC.WaitForPendingFinalizers()を呼び出す*

  • Com-server-applicationがユーザーが応答するのを待っているメッセージボックスを表示しているかどうかを確認します(ただし、Excelが閉じられない場合があることはわかりませんが、数回聞いたことがあります)

  • メインのExcelウィンドウにWM_CLOSEメッセージを送信する

  • 別のAppDomainでExcelと連携する機能を実行する。 AppDomainがアンロードされると、Excelインスタンスが閉じられると考える人もいます。

  • Excel相互運用コードの開始後にインスタンス化されたすべてのExcelインスタンスを強制終了します。

BUT!これらすべてのオプションが役に立たない、または適切でないことがあります。

たとえば、昨日、私は自分の関数の1つ(Excelで動作する)で、関数が終了した後もExcelが実行され続けることを知りました。私はすべて試しました!私は徹底的に全体の機能を10回チェックして、すべてのためにMarshal.FinalReleaseComObject()を加えました!私はGC.Collect()とGC.WaitForPendingFinalizers()も持っていました。隠しメッセージボックスをチェックしました。 WM_CLOSEメッセージをメインのExcelウィンドウに送信しようとしました。私は自分の機能を別の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が閉じます=)

203
nightcoder

UPDATE:C#コードを追加し、Windowsの求人へのリンク

私はこの問題を解決するためにしばらく時間をかけましたが、当時はXtremeVBTalkが最もアクティブで反応が早いものでした。これは私の元の記事へのリンクです。 あなたのアプリケーションがクラッシュしたとしても、Excel Interopプロセスをきれいに閉じる 。以下は投稿の概要と、この投稿にコピーされたコードです。

  • Application.Quit()Process.Kill()を使ってInteropプロセスを閉じることは大部分はうまくいきますが、アプリケーションが壊滅的にクラッシュした場合は失敗します。すなわちアプリがクラッシュしても、Excelプロセスは緩やかに動作し続けます。
  • 解決策は、 Windows Job Objects を介してOSにWin32呼び出しを使用してプロセスのクリーンアップを処理させることです。メインアプリケーションが停止すると、関連付けられているプロセス(Excelなど)も終了します。

OSが実際のクリーンアップ作業を行っているため、これはクリーンな解決策であることがわかりました。あなたがしなければならないのはregister 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 enum値で、この値は _ 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);
46
joshgo

これは私が取り組んでいたプロジェクトのために働きました:

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

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

35
Philip Fourie

Excelネームスペースにあるものはすべてリリースする必要があります。期間

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

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

あなたがしなければならない

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

続いてオブジェクトを解放します。

29
MagicKat

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

使用法:

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/

18
Edward Wilde

最初に - あなたは 決して Excelの相互運用を行うときにMarshal.ReleaseComObject(...)またはMarshal.FinalReleaseComObject(...)を呼び出さなければなりません。これは紛らわしいアンチパターンですが、Microsoftからの情報を含め、.NETから手動でCOM参照を解放する必要があることを示す情報はすべて間違っています。実際、.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やStack Overflow(とくにこの質問)に関する多くの投稿を含む、この問題についての誤った情報や混乱がたくさんあります。

ブログ記事Marshal.ReleaseComObjectと考えられていますデバッガの下で生存し続けている参照による問題の発見と一緒になって、私の以前の混乱を招きましたテスト.

18
Govert

私はこの問題が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プログラムを終了するように指示する必要があります。したがって、リンクが解放されても開いたままになります。

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

15
Colin

一般的な開発者、あなたの解決策のどれも私のために働いていなかったので、私は新しい トリック を実装することにしました。

まず「私たちの目標は何ですか?」と指定しましょう。 => "タスクマネージャでの仕事の後にExcelオブジェクトを見ないようにする"

OK。挑戦してそれを破壊し始めるためにいいえを許さないでください、しかし並行して走っている他のインスタンスやExcelを破壊しないことを考えてください。

だから、現在のプロセッサのリストを取得し、ExcelのプロセスのPIDを取得し、あなたの仕事が完了したら、私たちはユニークなPIDを持つプロセスのリストに新しいゲストを持って、その1つだけを見つけて破壊する。

<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();

これで私の問題は解決しました、あなたも願っています。

12
Mohsen Afshin

これは確かに複雑すぎたようです。私の経験からすると、Excelを正常に終了させるには、3つの重要な点があります。

1:作成したExcelアプリケーションへの参照が残っていないことを確認します(いずれにしても1つだけ必要です。nullに設定します)。

2:GC.Collect()を呼び出す

3:ユーザーが手動でプログラムを閉じるか、またはExcelオブジェクトでQuitを呼び出すことによって、Excelを閉じる必要があります。 (Quitは、ユーザーがプログラムを閉じようとしたときと同じように機能し、保存されていない変更がある場合は確認ダイアログを表示します。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がクリーンアップされる前にプログラムを中断した場合(通常、プログラムのデバッグ中に "stop"を押すことによって)、Excel.exeプロセスが蓄積されることがあります。

10
Dave Cousineau

読み取り、作成時に各オブジェクトへの直接参照を作成しても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
9
Grimfort

試した後

  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();
    }
}
9
D.G.

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

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アプリケーションでは、どれほど頻繁にガベージコレクタを呼び出しても、Excelを使用できなくても問題ありません。まだサーバー上で実行されています。

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

9
Chris McGrath

私は伝統的に VVSの答え にあるアドバイスに従った。しかし、この答えを最新の選択肢で最新に保つために、私の将来のプロジェクトはすべて「NetOffice」ライブラリを使用すると思います。

NetOffice は、Office PIAの完全な代替品であり、完全にバージョンに依存しません。これは、.NETでMicrosoft Officeを操作するときによく発生するクリーンアップを処理できるManaged COMラッパーのコレクションです。

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

  • 主にバージョンに依存しません(そしてバージョンに依存する機能は文書化されています)
  • 依存関係なし
  • PIAなし
  • 登録なし
  • VSTOなし

私はこのプロジェクトとは全く関係がありません。頭痛が大幅に軽減されたことを心から感謝します。

8
BTownTKD

私はこれに正確に従った...しかし私はまだ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
craigtadlock

¨°º¤ø„ Shoot 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);
}
7

「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はコールバックインタフェースを実装するクラスの an インスタンスを作成し、それをExcelに登録します。

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

イベント処理のためのコールバックインターフェイスは、PIAがすべての.NETイベントサブスクリプション要求に対してすべてのインターフェイスイベントをサブスクライブする必要があることを意味します。選べません。イベントコールバックを受け取ると、コールバックオブジェクトは関連する.NETイベントハンドラが現在のイベントに関心があるかどうかをチェックしてからハンドラを呼び出すかコールバックを黙って無視します。

COMインスタンスの参照数への影響

これらのすべてのコールバックオブジェクトは、それらが(パラメータとして)受け取るすべてのCOMオブジェクトの参照カウントを(静かに無視されるものであっても)減らしません。それらはCOMオブジェクトを解放するために _ clr _ ガベージコレクタのみに依存します。

GCの実行は非決定論的なので、これはExcelプロセスを望みよりも長い期間中断することにつながり、「メモリリーク」の印象を与える可能性があります。

溶液

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

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

6
Amit Mittal

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

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

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相互運用機能の大きな欠点です。もっと複雑なシナリオでは、VB6や他の管理されていない言語でActiveX DLLを書くことを真剣に検討してください。その後、.NETアプリケーションからこのActiveX DLLを参照できます。この参照を1つ解放するだけで済みますので、作業ははるかに簡単になります。

5
Joe

上記のすべてがうまくいかなかったときは、Excelにシートを閉じる時間を与えてみてください。

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

...
FinalReleaseComObject(app);
4
spiderman

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

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

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

オプションは一緒です ここ

3
Ned

Word/Excel相互運用アプリケーションを使用するときは十分に注意してください。すべての解決策を試した後、我々はまだ(2000人以上のユーザーで)サーバー上で開いたままになっているたくさんの "WinWord"プロセスを持っていました。

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

だから私はこの問題に対する絶対的な解決策はないが、 Office Open XML developmentのような他の方法に切り替えることはないと思う。

2
Arvand

COMオブジェクトのリリースに関する素晴らしい記事は2.5 COMオブジェクトのリリース(MSDN)です。

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

これにより、 every 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参照を無効にしてガベージコレクションを強制すると正しくクリーンアップされます。 GC.WaitForPendingFinalizersも呼び出すと、ガベージコレクションは可能な限り決定的になります。つまり、WaitForPendingFinalizersへの2回目の呼び出しから戻ったときに、オブジェクトがクリーンアップされた時点を正確に確認できます。代わりに、Marshal.ReleaseComObjectを使用することもできます。ただし、この方法を使用する必要はほとんどありません。

2
Porkbutts

2ドットルールは私にはうまくいきませんでした。私の場合は、次のように自分のリソースをきれいにする方法を作成しました。

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

私は現在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
Tom Brearley

そのうちのいくつかは、フレームワークがOfficeアプリケーションを処理する方法にすぎないと思いますが、私は間違っている可能性があります。場合によっては、一部のアプリケーションがプロセスをただちにクリーンアップし、その他の日はアプリケーションが閉じるまで待つように見えます。一般的に、私は細部に注意を払うのをやめて、その日の終わりに他のプロセスが浮かんでいないことを確認するだけです。

また、多分私は物事を単純化しすぎていますが、私はあなたができるだけだと思います...

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
bill_the_loser

'これは確かに過度に複雑になったようです。私の経験からすると、Excelを正常に終了させるには、3つの重要な点があります。

1:作成したExcelアプリケーションへの参照が残っていないことを確認します(いずれにしても1つしかないはずです。nullに設定してください)。

2:GCを呼び出す。Collect()

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

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

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

それはあなたのプログラムの事の側面からExcelを一掃するでしょう。 Excelが(ユーザーによって手動で、またはQuitを呼び出すことによって)閉じられると、プロセスは終了します。プログラムがすでに閉じられている場合、プロセスはGC.Collect()呼び出しで消えます。

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

これは何年も問題なく私のために働いてきました。ただし、これは機能しますが、実際に機能するには適切に閉じる必要があります。 Excelをクリーンアップする前にプログラムを中断した場合は、Excel.exeプロセスが蓄積されることになります(通常、プログラムのデバッグ中に "stop"を押すことによって)。

受け入れられた答えは私のために働かなかった。デストラクタ内の次のコードがその仕事をしました。

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
Martin

つかいます:

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

宣言し、finallyブロックにコードを追加します。

finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (excelApp != null)
    {
        excelApp.Quit();
        int hWnd = excelApp.Application.Hwnd;
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        Process[] procs = Process.GetProcessesByName("Excel");
        foreach (Process p in procs)
        {
            if (p.Id == processID)
                p.Kill();
        }
        Marshal.FinalReleaseComObject(excelApp);
    }
}
1

私の解決策

[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();
}
1
Loart

おそらく既に書いているように、どのようにclose Excel(オブジェクト); open方法とプロジェクトの種類によっても重要です。

WPFアプリケーションでは、基本的に同じコードが問題なく、またはほとんど問題なく機能しています。

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

すべてのExcel関連の関数を基本クラスに、パーサーをサブクラスに配置します(異なるパーサーは一般的なExcel関数を使用します)。ジェネリックリストの各アイテムに対してExcelを再度開いたり閉じたりしたくないので、基本クラスで一度だけ開き、サブクラスで閉じました。コードをデスクトップアプリケーションに移動するときに問題が発生しました。上記のソリューションの多くを試しました。 GC.Collect()は既に提案されているように、以前に2回実装されていました。

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

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

1
Blaz Brencic

そこに私は考えがある、あなたが開いたExcelプロセスを殺すことを試みなさい:

  1. 優れたアプリケーションを開く前に、oldProcessIdsという名前のすべてのプロセスIDを取得してください。
  2. excelapplicationを開きます。
  3. nowProcessIdsという名前のすべての優れたプロセスIDを取得します。
  4. 終了する必要があるときは、oldProcessIdsとnowProcessIdsの間のexcept IDを削除します。

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("Excel").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("Excel").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    
0
Aloha

C++/ATLオートメーションを使用して、ここにリストされている多くのものに別のソリューションを追加するだけです(私はあなたがVB/C#から同様のものを使用できると想像します)。

Excel::_ApplicationPtr pXL = ...
  :
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;

これは私にとって魅力のように働きます...

0
dicksters

VSTOアドインでアプリケーションオブジェクトを更新した後、PowerPointを閉じるのと同じ問題が発生しました。ここですべての答えを試しましたが、成功は限られていました。

これは私のケースで見つけた解決策です-DONTは「新しいアプリケーション」を使用します。ThisAddInのAddInBase基本クラスには既に「アプリケーション」へのハンドルがあります。そのハンドルを必要な場所で使用する(必要に応じて静的にする)場合、クリーンアップを心配する必要はなく、PowerPointが閉じてもハングアップしません。

0
swax

これを実行する本当に簡単な方法は次のとおりです。

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

int objExcelProcessId = 0;

Excel.Application objExcel = new Excel.Application();

GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);

Process.GetProcessById(objExcelProcessId).Kill();
0
tjarrett

Microsoft Excel 2016でテスト済み

本当にテスト済みの解決策。

C#への参照を参照してください: https://stackoverflow.com/a/1307180/10442623

VB.netへの参照に参照してください: https://stackoverflow.com/a/54044646/10442623

1クラスの仕事を含める

2 Excelプロセスの適切な処理を処理するためのクラスを実装する

0
SamSar

これまでのところ、すべての答えにこれらのいくつかが含まれているようです。

  1. プロセスを殺す
  2. GC.Collect()を使用してください
  3. すべてのCOMオブジェクトを追跡して正しく解放します。

これは私がこの問題がどれほど難しいかを理解させてくれます:)

私はExcelへのアクセスを簡単にするためにライブラリに取り組んできました、そしてそれを使っている人々が混乱を残さないことを確実にしようとしています(指が交差しました)。

Interopが提供するインターフェースに直接書くのではなく、私は拡張を容易にするための拡張メソッドを作っています。 ApplicationHelpers.CreateExcel()またはworkbook.CreateWorksheet( "mySheetNameThatWillBeValidated")のように。当然のことながら、作成されたものは後でクリーンアップする際に問題を引き起こす可能性があるため、最後の手段としてプロセスを強制終了することを実際に推奨します。それでも、適切にクリーンアップすること(3番目の選択肢)は、おそらく最も破壊的で最も制御されたものです。

だから、その文脈で私はそれがこのようなものを作るのが最善ではないかどうか疑問に思いました:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

使い捨てとの混同を避けるために「解放可能」を使用しました。これをIDisposableに拡張するのは簡単なはずです。

このような実装は:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

そして、あらゆる種類のCOMオブジェクトに対して同様のことをすることができます。

ファクトリメソッドで:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var Excel = new ApplicationContainer().Releasible;
        Excel.Visible = !hidden;

        return Excel;
    }

すべてのコンテナがGCによって適切に破棄されることを期待しているので、自動的にQuitMarshal.FinalReleaseComObjectを呼び出します。

コメント?それとも第三種の質問に対する答えですか?

0
akirakonenu