Question Comment puis-je nettoyer correctement les objets d'interopérabilité Excel?


J'utilise l'interop Excel dans C # (ApplicationClass) et j'ai placé le code suivant dans ma clause finally:

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

Bien que ce genre de travaux, le Excel.exe processus est toujours en arrière-plan, même après que je ferme Excel. Il est seulement libéré une fois que mon application est fermée manuellement.

Qu'est-ce que je fais de mal, ou existe-t-il une alternative pour s'assurer que les objets interop sont correctement éliminés?


674
2017-10-01 17:18


origine


Réponses:


Excel ne se ferme pas car votre application contient toujours des références aux objets COM.

Je suppose que vous appelez au moins un membre d'un objet COM sans l'affecter à une variable.

Pour moi c'était le excelApp.Worksheets objet que j'ai directement utilisé sans l'assigner à une variable:

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

Je ne savais pas que C # interne a créé un emballage pour le Des feuilles de calcul COM objet qui n'a pas été publié par mon code (parce que je n'étais pas au courant) et était la cause pourquoi Excel n'a pas été déchargé.

J'ai trouvé la solution à mon problème sur cette page, qui a aussi une bonne règle pour l'utilisation des objets COM en C #:

N'utilisez jamais deux points avec des objets COM.


Donc, avec cette connaissance, la bonne façon de faire ce qui précède est:

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

642
2017-10-01 17:30



Vous pouvez réellement libérer votre objet Application Excel, mais vous devez en prendre soin.

Le conseil de conserver une référence nommée pour absolument chaque objet COM auquel vous accédez, puis de le libérer explicitement via Marshal.FinalReleaseComObject() est correct en théorie, mais, malheureusement, très difficile à gérer dans la pratique. Si jamais on glisse n'importe où et utilise "deux points", ou itère les cellules via un for each loop, ou tout autre type de commande similaire, alors vous aurez des objets COM non référencés et risquez un blocage. Dans ce cas, il n'y aurait aucun moyen de trouver la cause dans le code; vous devrez revoir tout votre code à la vue et, espérons-le, trouver la cause, une tâche qui pourrait être presque impossible pour un grand projet.

La bonne nouvelle est que vous n'avez pas besoin de gérer une référence de variable nommée pour chaque objet COM que vous utilisez. Appelez plutôt GC.Collect() et alors GC.WaitForPendingFinalizers() pour libérer tous les objets (généralement mineurs) auxquels vous ne détenez pas de référence, puis libérez explicitement les objets sur lesquels vous détenez une référence de variable nommée.

Vous devez également publier vos références nommées en ordre inverse d'importance: les objets range d'abord, puis les feuilles de calcul, les classeurs et enfin votre objet Application Excel.

Par exemple, en supposant que vous ayez une variable d'objet Range nommée xlRng, une variable Worksheet nommée xlSheet, une variable de classeur nommée xlBook et une variable d'application Excel nommée xlApp, alors votre code de nettoyage pourrait ressembler à ceci:

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

Dans la plupart des exemples de code, vous verrez pour nettoyer les objets COM de .NET, le GC.Collect() et GC.WaitForPendingFinalizers() les appels sont faits DEUX FOIS comme dans:

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

Toutefois, cela ne devrait pas être requis, sauf si vous utilisez Visual Studio Tools pour Office (VSTO), qui utilise des finaliseurs qui provoquent la promotion d'un graphique entier d'objets dans la file d'attente de finalisation. De tels objets ne seraient pas libérés avant que prochain collecte des ordures. Cependant, si vous n'utilisez pas VSTO, vous devriez pouvoir appeler GC.Collect() et GC.WaitForPendingFinalizers() juste une fois.

Je sais que l'appel explicite GC.Collect() est un non-non (et certainement le faire deux fois semble très douloureux), mais il n'y a pas moyen de le contourner, pour être honnête. Par des opérations normales, vous générez des objets cachés auxquels vous ne tenez aucune référence que vous ne pouvez donc pas libérer par d'autres moyens que d'appeler GC.Collect().

C'est un sujet complexe, mais c'est vraiment tout ce qu'il y a à faire. Une fois que vous avez établi ce modèle pour votre procédure de nettoyage, vous pouvez coder normalement, sans avoir besoin de wrappers, etc. :-)

J'ai un tutoriel sur ceci ici:

Automatisation des programmes Office avec VB.Net / COM Interop

Il est écrit pour VB.NET, mais ne soyez pas rebutés par cela, les principes sont exactement les mêmes que lorsque vous utilisez C #.


263
2017-12-12 14:50



Préface: ma réponse contient deux solutions, alors faites attention en lisant et ne manquez rien.

Il existe différents moyens et conseils pour décharger l'instance Excel, tels que:

  • Libérer TOUS les objets com explicitement avec Marshal.FinalReleaseComObject () (sans oublier implicitement com-objets créés). Pour libérer chaque objet com créé, vous pouvez utiliser la règle de 2 points mentionnée ici:
    Comment puis-je nettoyer correctement les objets d'interopérabilité Excel?

  • Appel de GC.Collect () et GC.WaitForPendingFinalizers () à faire CLR libère des com-objects inutilisés * (En fait, ça marche, voir ma deuxième solution pour plus de détails)

  • Vérifier si l'application com-server peut-être montre une boîte de message en attente de l'utilisateur à répondre (même si je ne suis pas sûr qu'il peut empêcher Excel de fermeture, mais j'en ai entendu parler quelques-uns fois)

  • Envoyer le message WM_CLOSE au principal Fenêtre Excel

  • Exécuter la fonction qui fonctionne avec Excel dans un AppDomain distinct. Certaines personnes croient que l'instance Excel sera fermé, lorsque AppDomain est déchargé.

  • Tuer toutes les instances d'Excel qui ont été instanciées après le début de notre code Excel.

MAIS! Parfois, toutes ces options n'aident pas ou ne peuvent pas être appropriées!

Par exemple, hier, j'ai découvert que dans l'une de mes fonctions (qui fonctionne avec Excel) Excel continue de fonctionner après la fin de la fonction. J'ai tout essayé! J'ai vérifié minutieusement toute la fonction 10 fois et ajouté Marshal.FinalReleaseComObject () pour tout! J'ai également eu GC.Collect () et GC.WaitForPendingFinalizers (). J'ai vérifié les boîtes de message cachées. J'ai essayé d'envoyer le message WM_CLOSE à la fenêtre principale d'Excel. J'ai exécuté ma fonction dans un AppDomain distinct et déchargé ce domaine. Rien n'a aidé! L'option de fermeture de toutes les instances d'Excel est inappropriée, car si l'utilisateur démarre une autre instance Excel manuellement, lors de l'exécution de ma fonction qui fonctionne également avec Excel, cette instance sera également fermée par ma fonction. Je parie que l'utilisateur ne sera pas content! Donc, honnêtement, c'est une option boiteuse (pas d'attaque les gars). J'ai donc passé quelques heures avant de trouver un bon (à mon humble avis) Solution: Tuer le processus Excel par hWnd de sa fenêtre principale(c'est une première solution).

Voici le code simple:

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

Comme vous pouvez le voir, j'ai fourni deux méthodes, selon le modèle Try-Parse (je pense que c'est approprié ici): une méthode ne lance pas d'exception si le Process n'a pas pu être tué (par exemple le processus n'existe plus) et une autre méthode émet une exception si le processus n'a pas été tué. Le seul point faible de ce code est les autorisations de sécurité. Théoriquement, l'utilisateur n'a peut-être pas les autorisations pour tuer le processus, mais dans 99,99% des cas, l'utilisateur dispose de telles autorisations. J'ai également testé avec un compte invité - cela fonctionne parfaitement.

Ainsi, votre code, fonctionnant avec Excel, peut ressembler à ceci:

int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);

Voila! Excel est terminé! :)

Ok, revenons à la deuxième solution, comme je l'ai promis au début de l'article. La deuxième solution consiste à appeler GC.Collect () et GC.WaitForPendingFinalizers (). Oui, ils fonctionnent réellement, mais vous devez faire attention ici!
Beaucoup de gens disent (et j'ai dit) qu'appeler GC.Collect () n'aide pas. Mais la raison pour laquelle cela n'aiderait pas, c'est s'il existe encore des références aux objets COM! L'une des raisons les plus populaires pour GC.Collect () n'est pas utile est l'exécution du projet en mode Debug. En mode debug, les objets qui ne sont plus réellement référencés ne seront plus récupérés avant la fin de la méthode.
Donc, si vous avez essayé GC.Collect () et GC.WaitForPendingFinalizers () et cela n'a pas aidé, essayez de faire ce qui suit:

1) Essayez d'exécuter votre projet en mode Release et vérifiez si Excel s'est fermé correctement

2) Envelopper la méthode fonctionnant avec Excel dans une méthode séparée. Donc, au lieu de quelque chose comme ça:

void GenerateWorkbook(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

vous écrivez:

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)
    ...
  }
}

Maintenant, Excel va fermer =)


199
2017-08-20 16:00



METTRE À JOUR: Ajout du code C # et lien vers les Jobs Windows

J'ai passé quelque temps à essayer de comprendre ce problème, et à l'époque XtremeVBTalk était le plus actif et réactif. Voici un lien vers mon message original, Fermer proprement un processus Excel Interop, même si votre application plante. Vous trouverez ci-dessous un résumé de la publication et le code copié sur ce post.

  • Fermeture du processus Interop avec Application.Quit() et Process.Kill() fonctionne pour la plupart, mais échoue si les applications se bloque de façon catastrophique. C'est à dire. Si l'application se bloque, le processus Excel sera toujours libre.
  • La solution consiste à laisser le système d'exploitation gérer le nettoyage de vos processus via Objets de travail Windows en utilisant des appels Win32. Lorsque votre application principale meurt, les processus associés (c'est-à-dire Excel) se terminent également.

J'ai trouvé que c'était une solution propre parce que le système d'exploitation fait un vrai travail de nettoyage. Tout ce que tu dois faire est registre le processus Excel.

Code de travail Windows

Enveloppe les appels API Win32 pour enregistrer les processus Interop.

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

}

Remarque sur le code du constructeur

  • Dans le constructeur, le info.LimitFlags = 0x2000; est appelé. 0x2000 est le JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE valeur enum, et cette valeur est définie par MSDN comme:

Provoque l'arrêt de tous les processus associés au travail lorsque le   la dernière poignée du travail est fermée.

Appel API Win32 supplémentaire pour obtenir l'ID de processus (PID)

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

En utilisant le code

    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
2017-10-01 17:30



Cela a fonctionné pour un projet sur lequel je travaillais:

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

Nous avons appris qu'il était important de définir chaque référence à un objet COM Excel à null lorsque vous avez terminé avec elle. Cela inclus les cellules, les feuilles et tout.


34
2017-10-01 17:45



Tout ce qui se trouve dans l'espace de noms Excel doit être libéré. Période

Vous ne pouvez pas faire:

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

Vous devez faire

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

suivi de la libération des objets.


29
2017-12-08 11:10



je a trouvé un modèle générique utile qui peut aider à implémenter le modèle d'élimination correct pour les objets COM, qui nécessitent Marshal.ReleaseComObject quand ils sortent de la portée:

Usage:

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

Modèle:

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;
        }
    }
}

Référence:

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


18
2017-11-21 03:41



Je ne peux pas croire que ce problème a hanté le monde pendant 5 ans .... Si vous avez créé une application, vous devez l'arrêter avant de supprimer le lien.

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

lors de la fermeture

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

Lorsque vous créez une application Excel, elle ouvre un programme Excel en arrière-plan. Vous devez commander à ce programme Excel de quitter avant de relâcher le lien car ce programme Excel ne fait pas partie de votre contrôle direct. Par conséquent, il restera ouvert si le lien est libéré!

Bonne programmation tout le monde ~~


15
2018-06-29 22:50



D'abord - vous jamais devoir appeler Marshal.ReleaseComObject(...) ou Marshal.FinalReleaseComObject(...) en faisant Excel interop. C'est un anti-pattern déroutant, mais toute information à ce sujet, y compris de la part de Microsoft, qui indique que vous devez manuellement publier des références COM à partir de .NET est incorrecte. Le fait est que le runtime .NET et le garbage collector suivent et nettoient correctement les références COM. Pour votre code, cela signifie que vous pouvez supprimer toute la boucle while (...) en haut.

Deuxièmement, si vous voulez vous assurer que les références COM à un objet COM hors processus sont nettoyées lorsque votre processus se termine (afin que le processus Excel se ferme), vous devez vous assurer que le garbage collector s'exécute. Vous faites cela correctement avec les appels à GC.Collect() et GC.WaitForPendingFinalizers(). L'appeler deux fois est sûr, et assure que les cycles sont définitivement nettoyés aussi (même si je ne suis pas sûr que c'est nécessaire, et apprécierait un exemple qui montre cela).

Troisièmement, lors de l'exécution sous le débogueur, les références locales seront maintenues artificiellement en vie jusqu'à la fin de la méthode (de sorte que l'inspection des variables locales fonctionne). Alors GC.Collect() les appels ne sont pas efficaces pour nettoyer l'objet comme rng.Cells de la même méthode. Vous devez scinder le code en faisant l'interopérabilité COM du nettoyage du GC dans des méthodes séparées. (Ce fut une découverte clé pour moi, à partir d'une partie de la réponse postée ici par @nightcoder.)

Le schéma général serait donc:

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

Il y a beaucoup de fausses informations et de confusion sur ce problème, y compris de nombreux messages sur MSDN et sur Stack Overflow (et surtout cette question!).

Qu'est-ce qui m'a finalement convaincu de regarder de plus près et de trouver le bon conseil? Marshal.ReleaseComObject considéré comme dangereux avec trouver le problème avec des références maintenues en vie sous le débogueur qui confondait mes tests précédents.


13
2017-10-16 11:01