Pregunta ¿Cómo puedo limpiar adecuadamente los objetos de interoperabilidad de Excel?


Estoy usando la interoperabilidad de Excel en C # (ApplicationClass) y he colocado el siguiente código en mi cláusula finally:

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

Aunque este tipo de trabajo, el Excel.exe proceso aún está en segundo plano incluso después de cerrar Excel. Solo se lanza una vez que mi aplicación se cierra manualmente.

¿Qué estoy haciendo mal o existe una alternativa para asegurar que los objetos de interposición se desechen adecuadamente?


674
2017-10-01 17:18


origen


Respuestas:


Excel no se cierra porque su aplicación todavía tiene referencias a objetos COM.

Supongo que está invocando al menos un miembro de un objeto COM sin asignarlo a una variable.

Para mí fue el excelApp.Worksheets objeto que utilicé directamente sin asignarlo a una variable:

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

No sabía que internamente C # creó un contenedor para el Hojas de trabajo Objeto COM que mi código no publicó (porque no lo conocía) y fue la causa de que Excel no se descargara.

Encontré la solución a mi problema en esta página, que también tiene una buena regla para el uso de objetos COM en C #:

Nunca use dos puntos con objetos COM.


Entonces, con este conocimiento, la forma correcta de hacer lo anterior es:

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

642
2017-10-01 17:30



En realidad, puede liberar su aplicación de Excel de forma limpia, pero debe tener cuidado.

El consejo de mantener una referencia nombrada para absolutamente todos los objetos COM a los que accede y luego liberarlos explícitamente a través de Marshal.FinalReleaseComObject() es correcto en teoría, pero, desafortunadamente, es muy difícil de manejar en la práctica. Si alguna vez se desliza en cualquier lugar y utiliza "dos puntos", o itera las celdas a través de un for each loop, o cualquier otro tipo similar de comando, entonces tendrá objetos COM sin referencia y se arriesgará a ser colgado. En este caso, no habría forma de encontrar la causa en el código; Tendría que revisar todo su código a ojo y con suerte encontrar la causa, una tarea que podría ser casi imposible para un proyecto grande.

La buena noticia es que en realidad no tiene que mantener una referencia de variable nombrada para cada objeto COM que utilice. En cambio, llame GC.Collect() y entonces GC.WaitForPendingFinalizers() para liberar todos los objetos (generalmente menores) a los que no tiene una referencia, y luego liberar explícitamente los objetos a los que usted tiene una referencia de variable nombrada.

También debe publicar sus referencias nombradas en el orden inverso de importancia: rango de objetos primero, luego hojas de trabajo, libros de trabajo y finalmente su objeto Aplicación de Excel.

Por ejemplo, suponiendo que tiene una variable de objeto de rango llamada xlRng, una variable de hoja de trabajo llamada xlSheet, una variable Libro de trabajo llamada xlBook y una variable de aplicación de Excel llamada xlApp, entonces su código de limpieza podría ser similar al siguiente:

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

En la mayoría de los ejemplos de código que verá para limpiar objetos COM de .NET, GC.Collect() y GC.WaitForPendingFinalizers() las llamadas se hacen DOS VECES como en:

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

Sin embargo, esto no debería ser necesario a menos que use Visual Studio Tools para Office (VSTO), que utiliza finalizadores que provocan que se promueva un gráfico completo de objetos en la cola de finalización. Tales objetos no se liberarían hasta que siguiente recolección de basura. Sin embargo, si no está usando VSTO, debería poder llamar GC.Collect() y GC.WaitForPendingFinalizers() sólo una vez.

Sé que llamar explícitamente GC.Collect() es un no-no (y ciertamente hacerlo dos veces suena muy doloroso), pero no hay forma de evitarlo, para ser honesto. A través de las operaciones normales generará objetos ocultos a los que no tendrá ninguna referencia que, por lo tanto, no podrá divulgar a través de ningún otro medio que no sea el de llamar GC.Collect().

Este es un tema complejo, pero esto es todo lo que hay que hacer. Una vez que establezca esta plantilla para su procedimiento de limpieza, puede programar normalmente, sin la necesidad de envoltorios, etc. :-)

Tengo un tutorial sobre esto aquí:

Automatizar programas de Office con VB.Net / COM Interop

Está escrito para VB.NET, pero no se deje intimidar por eso, los principios son exactamente los mismos que cuando se usa C #.


263
2017-12-12 14:50



Prefacio: mi respuesta contiene dos soluciones, así que tenga cuidado al leer y no se pierda nada.

Hay diferentes maneras y consejos sobre cómo hacer que la instancia de Excel se descargue, como por ejemplo:

  • Liberando CADA objeto com explícitamente con Marshal.FinalReleaseComObject () (sin olvidar implícitamente creados com-objetos). Liberar cada objeto com creado, puede usar la regla de 2 puntos mencionados aquí:
    ¿Cómo puedo limpiar adecuadamente los objetos de interoperabilidad de Excel?

  • Llamar a GC.Collect () y GC.WaitForPendingFinalizers () para hacer CLR lanza com-objects sin usar * (En realidad, funciona, ver mi segunda solución para más detalles)

  • Comprobando si la aplicación com-server tal vez muestra un cuadro de mensaje esperando el usuario a responder (aunque yo no soy Seguro que puede evitar que Excel cerrando, pero escuché acerca de eso veces)

  • Enviando el mensaje WM_CLOSE al principal Ventana de Excel

  • Ejecutando la función que funciona con Excel en un dominio de aplicación separado. Algunas personas creen que la instancia de Excel se cerrará, cuando AppDomain sea descargado

  • Eliminando todas las instancias de Excel que se crearon una instancia después de que se inició nuestro código de interoperación de Excel.

¡PERO! A veces, todas estas opciones simplemente no ayudan o no pueden ser apropiadas.

Por ejemplo, ayer descubrí que en una de mis funciones (que funciona con Excel) Excel sigue ejecutándose después de que la función finaliza. ¡Intenté todo! Completamente comprobé la función completa 10 veces y agregué Marshal.FinalReleaseComObject () para todo! También tuve GC.Collect () y GC.WaitForPendingFinalizers (). Revisé los cuadros de mensajes ocultos. Intenté enviar el mensaje WM_CLOSE a la ventana principal de Excel. Ejecuté mi función en un AppDomain separado y descargué ese dominio. ¡Nada ayudó! La opción de cerrar todas las instancias de Excel es inapropiada, porque si el usuario inicia otra instancia de Excel manualmente, durante la ejecución de mi función que también funciona con Excel, esa función también se cerrará. ¡Apuesto a que el usuario no estará feliz! Entonces, honestamente, esta es una opción poco convincente (sin ofender chicos). Así que pasé un par de horas antes de encontrar un buen (en mi humilde opinión) solución: Matar el proceso de excel por hWnd de su ventana principal(es una primera solución).

Aquí está el código 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();
}

Como puede ver, proporcioné dos métodos, de acuerdo con el patrón Try-Parse (creo que es apropiado aquí): un método no arroja una excepción si el proceso no se pudo eliminar (por ejemplo, el proceso ya no existe), y otro método arroja una excepción si el proceso no se eliminó. El único lugar débil en este código son los permisos de seguridad. Teóricamente, el usuario puede no tener permisos para matar el proceso, pero en el 99.99% de los casos el usuario tiene esos permisos. También lo probé con una cuenta de invitado; funciona perfectamente.

Entonces, su código, trabajando con Excel, puede verse así:

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 se termina! :)

Ok, volvamos a la segunda solución, como prometí al comienzo de la publicación. La segunda solución es llamar a GC.Collect () y GC.WaitForPendingFinalizers (). Sí, realmente funcionan, pero debes tener cuidado aquí.
Mucha gente dice (y dije) que llamar a GC.Collect () no ayuda. ¡Pero la razón por la que no ayudaría es si todavía hay referencias a objetos COM! Una de las razones más populares para que GC.Collect () no sea útil es ejecutar el proyecto en modo Debug. En objetos de modo de depuración a los que ya no se hace referencia en realidad, no se recolectarán elementos no utilizados hasta el final del método.
Por lo tanto, si intentó GC.Collect () y GC.WaitForPendingFinalizers () y no sirvió, intente hacer lo siguiente:

1) Intenta ejecutar tu proyecto en modo de lanzamiento y comprueba si Excel cerró correctamente

2) Envuelva el método trabajando con Excel en un método diferente. Entonces, en lugar de algo como esto:

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

usted escribe:

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

Ahora, Excel cerrará =)


199
2017-08-20 16:00



ACTUALIZAR: Se agregó código C # y se vincula a los trabajos de Windows

Pasé algún tiempo tratando de resolver este problema, y ​​en ese momento XtremeVBTalk era el más activo y receptivo. Aquí hay un enlace a mi publicación original, Cerrando un proceso de Interoperación de Excel limpiamente, incluso si su aplicación falla. A continuación se muestra un resumen de la publicación y el código copiado en esta publicación.

  • Cerrando el proceso de Interop con Application.Quit() y Process.Kill() funciona en su mayor parte, pero falla si las aplicaciones fallan catastróficamente. Es decir. si la aplicación falla, el proceso de Excel seguirá suelto.
  • La solución es permitir que el SO maneje la limpieza de sus procesos a través de Objetos de trabajo de Windows usando llamadas Win32. Cuando su aplicación principal fallezca, los procesos asociados (es decir, Excel) también finalizarán.

Encontré esto como una solución limpia porque el sistema operativo está haciendo un verdadero trabajo de limpieza. Todo lo que tienes que hacer es registro el proceso de Excel.

Código de trabajo de Windows

Envuelve las llamadas a la API de Win32 para registrar procesos de interoperabilidad.

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

}

Nota sobre el código de Constructor

  • En el constructor, el info.LimitFlags = 0x2000; se llama. 0x2000 es el JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE valor enum, y este valor está definido por MSDN como:

Hace que todos los procesos asociados con el trabajo finalicen cuando el   el último identificador para el trabajo está cerrado.

Llamada API extra Win32 para obtener el ID de proceso (PID)

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

Usando el código

    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



Esto funcionó para un proyecto en el que estaba trabajando:

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

Aprendimos que era importante establecer cada referencia a un objeto COM de Excel para anular cuando haya terminado con él. Esto incluyó Celdas, Hojas y todo.


34
2017-10-01 17:45



Todo lo que está en el espacio de nombres de Excel debe ser lanzado. Período

No puedes estar haciendo:

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

Tienes que estar haciendo

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

seguido de la liberación de los objetos.


29
2017-12-08 11:10



yo encontró una plantilla genérica útil que puede ayudar a implementar el patrón de eliminación correcto para los objetos COM, que necesitan llamar a Marshal.ReleaseComObject cuando salen del alcance:

Uso:

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

Modelo:

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

Referencia:

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


18
2017-11-21 03:41



No puedo creer que este problema haya atormentado al mundo durante 5 años ... Si ha creado una aplicación, primero debe cerrarla antes de eliminar el enlace.

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

al cerrar

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

Cuando se inicia una aplicación Excel, se abre un programa Excel en segundo plano. Debe ordenar que el programa Excel se cierre antes de liberar el enlace porque ese programa Excel no forma parte de su control directo. Por lo tanto, permanecerá abierto si se lanza el enlace.

Buena programación para todos ~~


15
2018-06-29 22:50



Primero tú Nunca tiene que llamar Marshal.ReleaseComObject(...) o Marshal.FinalReleaseComObject(...) al hacer interoperabilidad de Excel. Es un anti-patrón confuso, pero cualquier información sobre esto, incluso de Microsoft, que indique que debe liberar manualmente las referencias COM de .NET es incorrecta. El hecho es que el .NET runtime y el recolector de basura correctamente realizan un seguimiento y limpian las referencias COM. Para su código, esto significa que puede eliminar todo el bucle `while (...) en la parte superior.

En segundo lugar, si desea asegurarse de que las referencias COM a un objeto COM fuera de proceso se limpien cuando finaliza su proceso (para que el proceso de Excel se cierre), debe asegurarse de que se ejecute el recolector de elementos no utilizados. Lo haces correctamente con llamadas a GC.Collect() y GC.WaitForPendingFinalizers(). Llamar a esto dos veces es seguro y garantiza que los ciclos se limpien definitivamente también (aunque no estoy seguro de que sea necesario, y agradecería un ejemplo que muestre esto).

Tercero, cuando se ejecuta bajo el depurador, las referencias locales se mantendrán artificialmente vivas hasta el final del método (para que funcione la inspección de variables locales). Asi que GC.Collect() las llamadas no son efectivas para limpiar objetos como rng.Cells del mismo método. Debe dividir el código haciendo la interoperabilidad COM desde la limpieza de GC en métodos separados. (Este fue un descubrimiento clave para mí, de una parte de la respuesta publicada aquí por @nightcoder).

El patrón general sería así:

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

Existe una gran cantidad de información falsa y confusión sobre este tema, incluidas muchas publicaciones en MSDN y en Stack Overflow (¡y especialmente esta pregunta!).

Lo que finalmente me convenció para tener una mirada más cercana y descubrir el consejo correcto fue una publicación de blog Marshal.ReleaseComObject considerado peligroso junto con encontrar el problema con referencias mantenidas vivas bajo el depurador que confundía mi prueba anterior.


13
2017-10-16 11:01



Desarrolladores comunes, ninguna de sus soluciones funcionó para mí, entonces decido implementar un nuevo truco.

Primero, especifique "¿Cuál es nuestro objetivo?" => "No ver el objeto excel después de nuestro trabajo en el administrador de tareas"

De acuerdo. Deje que no lo desafíe y empiece a destruirlo, pero considere no destruir otras instancias de Excel que se ejecutan en paralelo.

Por lo tanto, obtenga la lista de procesadores actuales y busque el PID de los procesos EXCEL, luego, una vez que haya terminado su trabajo, tenemos un nuevo invitado en la lista de procesos con un PID único, encuentre y destruya ese solo.

<tenga en cuenta que cualquier nuevo proceso de Excel durante su trabajo de Excel será detectado como nuevo y destruido>  <Una mejor solución es capturar el PID del nuevo objeto excel creado y simplemente destruirlo>

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

Esto resuelve mi problema, espero el tuyo también.


12
2017-12-17 20:15