787

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

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

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

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

4

43 に答える 43

703

アプリケーションがまだ 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でのガベージコレクションの理解とIDisposableを使用したExcel相互運用オブジェクトのクリーンアップ

于 2008-10-01T17:30:41.020 に答える
284

実際には、Excel Application オブジェクトをきれいに解放できますが、注意が必要です。

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

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

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

たとえば、 という名前の Range オブジェクト変数、 という名前xlRngのワークシート変数、 という名前xlSheetのワークブック変数、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()andGC.WaitForPendingFinalizers()を 1 回だけ呼び出すことができます。

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

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

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

VB.Net / COM Interop による Office プログラムの自動化

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

于 2008-10-01T19:54:21.410 に答える
226

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

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

  • Marshal.FinalReleaseComObject() を使用してすべての com オブジェクトを明示的に解放します (暗黙的に作成された com オブジェクトを忘れないでください)。作成されたすべての com オブジェクトを解放するには、ここに記載されている 2 つのドットのルールを使用できます:
    Excel 相互運用オブジェクトを適切にクリーンアップするにはどうすればよいですか?

  • GC.Collect() および GC.WaitForPendingFinalizers() を呼び出して、CLR が未使用の com オブジェクトを解放するようにする * (実際には機能します。詳細については、私の 2 番目のソリューションを参照してください)

  • com-server-application がユーザーの応答を待っているメッセージ ボックスを表示する可能性があるかどうかを確認します (ただし、Excel の終了を防ぐことができるかどうかはわかりませんが、何度か聞いたことがあります)。

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

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

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

しかし!場合によっては、これらすべてのオプションが役に立たなかったり、適切でなかったりすることがあります。

たとえば、昨日、関数の 1 つ (Excel で動作する) で、関数の終了後も Excel が実行され続けることがわかりました。私はすべてを試しました!関数全体を 10 回徹底的にチェックし、すべてに Marshal.FinalReleaseComObject() を追加しました。GC.Collect() と GC.WaitForPendingFinalizers() もありました。非表示のメッセージ ボックスを確認しました。メインの Excel ウィンドウに WM_CLOSE メッセージを送信しようとしました。別の AppDomain で関数を実行し、そのドメインをアンロードしました。何も助けませんでした!すべての Excel インスタンスを閉じるオプションは不適切です。ユーザーが別の Excel インスタンスを手動で開始すると、Excel でも機能する関数の実行中に、そのインスタンスも関数によって閉じられるためです。ユーザーは満足しないに違いない!したがって、正直なところ、これは不十分なオプションです (攻撃者はいません)。解決策:メイン ウィンドウの hWnd で Excel プロセスを強制終了します(これが最初の解決策です)。

簡単なコードは次のとおりです。

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

/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if(processID == 0) return false;
    try
    {
        Process.GetProcessById((int)processID).Kill();
    }
    catch (ArgumentException)
    {
        return false;
    }
    catch (Win32Exception)
    {
        return false;
    }
    catch (NotSupportedException)
    {
        return false;
    }
    catch (InvalidOperationException)
    {
        return false;
    }
    return true;
}

/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running). 
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if (processID == 0)
        throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
    Process.GetProcessById((int)processID).Kill();
}

ご覧のとおり、Try-Parse パターンに従って 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);

出来上がり!エクセル終了!:)

では、投稿の冒頭で約束したように、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 が終了します =)

于 2009-12-12T14:50:15.970 に答える
51

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

私はこの問題を解決するためにしばらく時間を費やしましたが、その時点では XtremeVBTalk が最もアクティブで反応が良かったです。これは、私の元の投稿へのリンクです。アプリケーションがクラッシュした場合でも、Excel 相互運用プロセスをきれいに閉じる. 以下は、投稿の要約と、この投稿にコピーされたコードです。

  • と を使用して相互運用プロセスを閉じるとApplication.Quit()Process.Kill()ほとんどの場合は機能しますが、アプリケーションが壊滅的にクラッシュした場合は失敗します。つまり、アプリがクラッシュした場合でも、Excel プロセスはまだ緩んでいます。
  • 解決策は、OS がWin32 呼び出しを使用してWindows ジョブ オブジェクトを介してプロセスのクリーンアップを処理できるようにすることです。メイン アプリケーションが終了すると、関連するプロセス (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);
于 2009-08-20T16:00:37.047 に答える
45

これは、私が取り組んでいたプロジェクトで機能しました:

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

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

于 2008-10-01T17:30:36.217 に答える
38

まず、Excel の相互運用を行うときに、または呼び出す必要はありません。これは紛らわしいアンチパターンですが、.NET から手動で COM 参照を解放する必要があることを示す Microsoft からの情報を含め、これに関する情報は正しくありません。実際、.NET ランタイムとガベージ コレクターは、COM 参照を正しく追跡してクリーンアップします。コードの場合、これは先頭の `while (...) ループ全体を削除できることを意味します。Marshal.ReleaseComObject(...)Marshal.FinalReleaseComObject(...)

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

第 3 に、デバッガーで実行する場合、ローカル参照はメソッドの最後まで人工的に保持されます (ローカル変数の検査が機能するようにするため)。その ため、同じメソッドからのGC.Collect()ようにオブジェクトをクリーニングする場合、呼び出しは効果的ではありません。rng.CellsGC クリーンアップからの COM 相互運用を実行するコードを別のメソッドに分割する必要があります。(これは、@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 が危険であると見なされたというブログ投稿でした。

于 2016-06-29T22:50:23.497 に答える
31

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

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

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

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

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

その後、オブジェクトを解放します。

于 2008-10-01T17:45:45.213 に答える
19

スコープ外になったときに 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/

于 2008-12-08T11:10:50.320 に答える
17

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

皆さん良いプログラミング~~

于 2010-11-21T03:41:13.067 に答える
13

一般的な開発者の皆さん、あなたの解決策はどれもうまくいきませんでした。そのため、新しいトリックを実装することにしました。

まず「私たちの目標は何ですか?」を明確にしましょう。=> 「タスク マネージャーで作業を行った後、Excel オブジェクトが表示されない」

Ok。挑戦せずに破壊を開始しますが、並行して実行されている他のインスタンス os 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();

これで私の問題は解決します。あなたの問題も解決します。

于 2009-10-16T11:01:36.883 に答える
11

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

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

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

  • ほとんどの場合、バージョンに依存しません (バージョンに依存する機能は文書化されています)。
  • 依存関係なし
  • いいえぴあ
  • 登録なし
  • いいえ

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

于 2013-03-28T14:12:39.593 に答える
10

読み取り、作成時に各オブジェクトへの直接参照を作成した場合でも、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
于 2010-12-06T13:08:35.833 に答える
9

ここで受け入れられた答えは正しいですが、「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 アプリケーションでは、ガベージ コレクターをどれだけ頻繁に呼び出しても、これらのいずれかを見逃すと、Excel はサーバー上でまだ実行されています。

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

于 2009-12-17T20:15:04.830 に答える
7
于 2013-06-14T15:16:58.820 に答える
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

于 2008-11-06T15:21:25.463 に答える
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 つのコールバック オブジェクト。

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

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

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

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

解決

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

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

于 2012-06-25T06:52:30.430 に答える
4

他の人が指摘しているように、使用するすべての 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 Interop の大きな欠点です。より複雑なシナリオでは、Office などのアウトプロセス COM オブジェクトとのすべての対話を委任できる VB6 またはその他のアンマネージ言語で ActiveX DLL を作成することを真剣に検討します。次に、この ActiveX DLL を .NET アプリケーションから参照できます。この 1 つの参照を解放するだけでよいので、作業はずっと簡単になります。

于 2008-10-01T18:26:09.857 に答える
3

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

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

...
FinalReleaseComObject(app);
于 2011-06-18T12:02:35.710 に答える
3

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

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

これにより、すべての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 を使用できます。ただし、この方法を使用する必要はほとんどないことに注意してください。

于 2013-01-11T01:46:22.340 に答える
3

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

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

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

オプションはこちらにまとめてあります。

于 2012-08-08T18:38:33.667 に答える
2

Word/Excel 相互運用アプリケーションの使用には細心の注意を払う必要があります。すべての解決策を試した後でも、サーバー上で多くの「WinWord」プロセスが開いたままになっていました (2000 人以上のユーザー)。

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

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

于 2012-03-14T15:47:35.893 に答える
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();
}
于 2014-06-18T09:34:16.183 に答える
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;
                    }
                }
            }
}
于 2016-08-02T14:32:38.500 に答える
1

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

[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();
于 2019-04-02T20:24:30.487 に答える
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 プロセスを維持するのも好きではありませんが、おそらくそれについて偏執的です。

于 2008-10-01T17:47:22.913 に答える
0

私は物事が自然にクリーンアップされるのが本当に好きです...それで、私のためにすべてのクリーンアップを行ういくつかのラッパークラスを作成しました! これらは、さらに下に文書化されています。

終了コードは非常に読みやすく、アクセスしやすいものです。Close()ワークブックとアプリケーションの後に実行されている Excel のファントム インスタンスをまだ見つけていませんQuit()(プロセスの途中でアプリをデバッグして閉じる場所以外に)。

function void OpenCopyClose() {
  var excel = new ExcelApplication();
  var workbook1 = excel.OpenWorkbook("C:\Temp\file1.xslx", readOnly: true);
  var readOnlysheet = workbook1.Worksheet("sheet1");

  var workbook2 = excel.OpenWorkbook("C:\Temp\file2.xslx");
  var writeSheet = workbook.Worksheet("sheet1");

  // do all the excel manipulation

  // read from the first workbook, write to the second workbook.
  var a1 = workbook1.Cells[1, 1];
  workbook2.Cells[1, 1] = a1

  // explicit clean-up
  workbook1.Close(false);
  workbook2 .Close(true);
  excel.Quit();
}

注:Close()とのQuit()呼び出しはスキップできますが、Excel ドキュメントに書き込む場合は、少なくともSave(). オブジェクトがスコープ外に出る (メソッドが戻る) と、クラス ファイナライザーが自動的に開始され、クリーンアップが実行されます。Worksheet COM オブジェクトからの COM オブジェクトへの参照は、変数のスコープに注意している限り、自動的に管理およびクリーンアップされます。たとえば、COM オブジェクトへの参照を格納する場合にのみ、変数を現在のスコープに対してローカルに保持します。必要に応じて、必要な値を POCO に簡単にコピーしたり、以下で説明するように追加のラッパー クラスを作成したりできます。

DisposableComObjectこれらすべてを管理するために、任意の COM オブジェクトのラッパーとして機能するクラス を作成しました。インターフェイスを実装しIDisposable、嫌いな人のためのファイナライザーも含まれていますusing

このDispose()メソッドは、プロパティを呼び出してから nullMarshal.ReleaseComObject(ComObject)に設定します。ComObjectRef

ComObjectRefプライベートプロパティが nullの場合、オブジェクトは破棄された状態です。

プロパティが破棄されたComObject後にアクセスされると、ComObjectAccessedAfterDisposeException例外がスローされます。

Dispose()メソッドは手動で呼び出すことができます。また、usingブロックの終了時、およびusing varその変数のスコープの終了時に、ファイナライザーによって呼び出されます。

、、、およびの最上位クラスはMicrosoft.Office.Interop.Excel、それぞれが のサブクラスである独自のラッパー クラスを取得します。ApplicationWorkbookWorksheetDisposableComObject

コードは次のとおりです。

/// <summary>
/// References to COM objects must be explicitly released when done.
/// Failure to do so can result in odd behavior and processes remaining running after the application has stopped.
/// This class helps to automate the process of disposing the references to COM objects.
/// </summary>
public abstract class DisposableComObject : IDisposable
{
    public class ComObjectAccessedAfterDisposeException : Exception
    {
        public ComObjectAccessedAfterDisposeException() : base("COM object has been accessed after being disposed") { }
    }

    /// <summary>The actual COM object</summary>
    private object ComObjectRef { get; set; }

    /// <summary>The COM object to be used by subclasses</summary>
    /// <exception cref="ComObjectAccessedAfterDisposeException">When the COM object has been disposed</exception>
    protected object ComObject => ComObjectRef ?? throw new ComObjectAccessedAfterDisposeException();

    public DisposableComObject(object comObject) => ComObjectRef = comObject;

    /// <summary>
    /// True, if the COM object has been disposed.
    /// </summary>
    protected bool IsDisposed() => ComObjectRef is null;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // in case a subclass implements a finalizer
    }

    /// <summary>
    /// This method releases the COM object and removes the reference.
    /// This allows the garbage collector to clean up any remaining instance.
    /// </summary>
    /// <param name="disposing">Set to true</param>
    protected virtual void Dispose(bool disposing)
    {
        if (!disposing || IsDisposed()) return;
        Marshal.ReleaseComObject(ComObject);
        ComObjectRef = null;
    }

    ~DisposableComObject()
    {
        Dispose(true);
    }
}

使い方を少し簡単にする便利な汎用サブクラスもあります。

public abstract class DisposableComObject<T> : DisposableComObject
{
    protected new T ComObject => (T)base.ComObject;

    public DisposableComObject(T comObject) : base(comObject) { }
}

最後にDisposableComObject<T>、Excel 相互運用クラスのラッパー クラスを作成するために使用できます。

サブクラスには、新しい Excel アプリケーション インスタンスへのExcelApplication参照があり、ワークブックを開くために使用されます。

OpenWorkbook()ExcelWorkbookDisposableComObject のサブクラスでもある を返します。

Dispose()Dispose()基本メソッド を呼び出す前に Excel アプリケーションを終了するようにオーバーライドされています。Quit()のエイリアスですDispose()

public class ExcelApplication : DisposableComObject<Application>
{
    public class OpenWorkbookActionCancelledException : Exception
    {
        public string Filename { get; }

        public OpenWorkbookActionCancelledException(string filename, COMException ex) : base($"The workbook open action was cancelled. {ex.Message}", ex) => Filename = filename;
    }

    /// <summary>The actual Application from Interop.Excel</summary>
    Application App => ComObject;

    public ExcelApplication() : base(new Application()) { }

    /// <summary>Open a workbook.</summary>
    public ExcelWorkbook OpenWorkbook(string filename, bool readOnly = false, string password = null, string writeResPassword = null)
    {
        try
        {
            var workbook = App.Workbooks.Open(Filename: filename, UpdateLinks: (XlUpdateLinks)0, ReadOnly: readOnly, Password: password, WriteResPassword: writeResPassword, );

            return new ExcelWorkbook(workbook);
        }
        catch (COMException ex)
        {
            // If the workbook is already open and the request mode is not read-only, the user will be presented
            // with a prompt from the Excel application asking if the workbook should be opened in read-only mode.
            // This exception is raised when when the user clicks the Cancel button in that prompt.
            throw new OpenWorkbookActionCancelledException(filename, ex);
        }
    }

    /// <summary>Quit the running application.</summary>
    public void Quit() => Dispose(true);

    /// <inheritdoc/>
    protected override void Dispose(bool disposing)
    {
        if (!disposing || IsDisposed()) return;
        App.Quit();
        base.Dispose(disposing);
    }
}

ExcelWorkbookもサブクラス化DisposableComObject<Workbook>し、ワークシートを開くために使用されます。

Worksheet()メソッドは、ご想像のExcelWorksheetとおり、 のサブクラスでもある を返しますDisposableComObject<Workbook>

Dispose()メソッドはオーバーライドされ、ベースを呼び出す前に最初にワークシートが閉じられますDispose()

注: を反復処理するために使用するいくつかの拡張メソッドを追加しましたWorkbook.Worksheets。コンパイル エラーが発生する場合は、これが原因です。最後に拡張メソッドを追加します。

public class ExcelWorkbook : DisposableComObject<Workbook>
{
    public class WorksheetNotFoundException : Exception
    {
        public WorksheetNotFoundException(string message) : base(message) { }
    }

    /// <summary>The actual Workbook from Interop.Excel</summary>
    Workbook Workbook => ComObject;

    /// <summary>The worksheets within the workbook</summary>
    public IEnumerable<ExcelWorksheet> Worksheets => worksheets ?? (worksheets = Workbook.Worksheets.AsEnumerable<Worksheet>().Select(w => new ExcelWorksheet(w)).ToList());
    private IEnumerable<ExcelWorksheet> worksheets;

    public ExcelWorkbook(Workbook workbook) : base(workbook) { }

    /// <summary>
    /// Get the worksheet matching the <paramref name="sheetName"/>
    /// </summary>
    /// <param name="sheetName">The name of the Worksheet</param>
    public ExcelWorksheet Worksheet(string sheetName) => Worksheet(s => s.Name == sheetName, () => $"Worksheet not found: {sheetName}");

    /// <summary>
    /// Get the worksheet matching the <paramref name="predicate"/>
    /// </summary>
    /// <param name="predicate">A function to test each Worksheet for a macth</param>
    public ExcelWorksheet Worksheet(Func<ExcelWorksheet, bool> predicate, Func<string> errorMessageAction) => Worksheets.FirstOrDefault(predicate) ??  throw new WorksheetNotFoundException(errorMessageAction.Invoke());

    /// <summary>
    /// Returns true of the workbook is read-only
    /// </summary>
    public bool IsReadOnly() => Workbook.ReadOnly;

    /// <summary>
    /// Save changes made to the workbook
    /// </summary>
    public void Save()
    {
        Workbook.Save();
    }

    /// <summary>
    /// Close the workbook and optionally save changes
    /// </summary>
    /// <param name="saveChanges">True is save before close</param>
    public void Close(bool saveChanges)
    {
        if (saveChanges) Save();
        Dispose(true);
    }

    /// <inheritdoc/>
    protected override void Dispose(bool disposing)
    {
        if (!disposing || IsDisposed()) return;
        Workbook.Close();
        base.Dispose(disposing);
    }
}

最後に、ExcelWorksheet.

UsedRows()Microsoft.Office.Interop.Excel.Rangeラップされていないオブジェクトの列挙型を返すだけです。オブジェクトのプロパティからアクセスされる COM オブジェクトMicrosoft.Office.Interop.Excel.WorksheetApplication、 、Workbook、およびWorksheet. これらはすべて、自動的にクリーンアップされるようです。ほとんどの場合、Ranges を反復処理して値を取得または設定するだけだったので、特定のユース ケースは利用可能な機能ほど高度ではありません。

Dispose()この場合、ワークシートに対して特別なアクションを実行する必要がないため、オーバーライドはありません。

public class ExcelWorksheet : DisposableComObject<Worksheet>
{
    /// <summary>The actual Worksheet from Interop.Excel</summary>
    Worksheet Worksheet => ComObject;

    /// <summary>The worksheet name</summary>
    public string Name => Worksheet.Name;

    // <summary>The worksheets cells (Unwrapped COM object)</summary>
    public Range Cells => Worksheet.Cells;

    public ExcelWorksheet(Worksheet worksheet) : base(worksheet) { }

    /// <inheritdoc cref="WorksheetExtensions.UsedRows(Worksheet)"/>
    public IEnumerable<Range> UsedRows() => Worksheet.UsedRows().ToList();
}

さらに多くのラッパー クラスを追加することができます。必要に応じてメソッドを追加ExcelWorksheetし、ラッパー クラスで COM オブジェクトを返すだけです。ExcelApplication.OpenWorkbook()と を介してワークブックをラップするときに行ったことをコピーするだけExcelWorkbook.WorkSheetsです。

いくつかの便利な拡張メソッド:

public static class EnumeratorExtensions
{
    /// <summary>
    /// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
    /// </summary>
    public static IEnumerable<T> AsEnumerable<T>(this IEnumerable enumerator)
    {
        return enumerator.GetEnumerator().AsEnumerable<T>();
    }

    /// <summary>
    /// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
    /// </summary>
    public static IEnumerable<T> AsEnumerable<T>(this IEnumerator enumerator)
    {
        while (enumerator.MoveNext()) yield return (T)enumerator.Current;
    }

    /// <summary>
    /// Converts the <paramref name="enumerator"/> to an IEnumerable of type <typeparamref name="T"/>
    /// </summary>
    public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator)
    {
        while (enumerator.MoveNext()) yield return enumerator.Current;
    }
}

public static class WorksheetExtensions
{
    /// <summary>
    /// Returns the rows within the used range of this <paramref name="worksheet"/>
    /// </summary>
    /// <param name="worksheet">The worksheet</param>
    public static IEnumerable<Range> UsedRows(this Worksheet worksheet) =>
        worksheet.UsedRange.Rows.AsEnumerable<Range>();
}
于 2021-11-19T06:33:47.097 に答える
-1

Excel は、C++ または C# を介してプログラミングするようには設計されていません。COM API は、Visual Basic、VB.NET、および VBA で動作するように特別に設計されています。

また、このページのすべてのコード サンプルは、各呼び出しがマネージド/アンマネージド境界を越える必要があるという単純な理由で最適ではありません。さらに、Excel COM API はRPCサーバーが忙しい。

私の意見では、Excel を自動化する最善の方法は、データを可能な限り大きな配列に収集し、これを VBA 関数またはサブ (を介してApplication.Run) に送信し、必要な処理を実行することです。さらに、呼び出すときApplication.Runは、Excel がビジーであることを示す例外を監視し、呼び出しを再試行してApplication.Runください。

于 2011-06-19T00:16:53.707 に答える
-1

これが私にとって本当にうまくいく唯一の方法です

        foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
        {
            proc.Kill();
        }
于 2017-02-14T07:55:42.520 に答える