التشغيل المتداخل في لغة C#:لا يتم الخروج من عملية Excel بعد إضافة ورقة عمل جديدة إلى الملف الموجود [نسخة مكررة]

StackOverflow https://stackoverflow.com/questions/350454

  •  20-08-2019
  •  | 
  •  

سؤال

التكرار المحتمل:
كيفية تنظيف كائنات التشغيل المتداخل لـ Excel بشكل صحيح في C#

لقد قرأت العديد من المواضيع الأخرى هنا حول إدارة مراجع COM أثناء استخدام التشغيل المتداخل لـ .Net-Excel للتأكد من خروج عملية Excel بشكل صحيح عند الخروج، وحتى الآن تعمل التقنيات بشكل جيد للغاية، لكنني عثرت مؤخرًا على حل مشكلة عند إضافة أوراق عمل جديدة إلى ملف مصنف موجود.

الكود أدناه يترك عملية zombie Excel.

إذا قمت بإضافة ورقة عمل إلى ملف مصنف تم إنشاؤه حديثًا، فسيتم الخروج بشكل جيد.إذا قمت بتشغيل الكود باستثناء .Add() الخط، فإنه يخرج بخير.(الملف الموجود الذي أقرأ منه هو ملف فارغ تم إنشاؤه بواسطة الكود الذي تم التعليق عليه)

أيه أفكار؟

//using Excel = Microsoft.Office.Interop.Excel;
//using System.Runtime.InteropServices;
public static void AddTest()
{
  string filename = @"C:\addtest.xls";
  object m = Type.Missing;
  Excel.Application excelapp = new Excel.Application();
  if (excelapp == null) throw new Exception("Can't start Excel");
  Excel.Workbooks wbs = excelapp.Workbooks;

  //if I create a new file and then add a worksheet,
  //it will exit normally (i.e. if you uncomment the next two lines
  //and comment out the .Open() line below):
  //Excel.Workbook wb = wbs.Add(Excel.XlWBATemplate.xlWBATWorksheet);
  //wb.SaveAs(filename, m, m, m, m, m, 
  //          Excel.XlSaveAsAccessMode.xlExclusive,
  //          m, m, m, m, m);

  //but if I open an existing file and add a worksheet,
  //it won't exit (leaves zombie excel processes)
  Excel.Workbook wb = wbs.Open(filename,
                               m, m, m, m, m, m,
                               Excel.XlPlatform.xlWindows,
                               m, m, m, m, m, m, m);

  Excel.Sheets sheets = wb.Worksheets;

  //This is the offending line:
  Excel.Worksheet wsnew = sheets.Add(m, m, m, m) as Excel.Worksheet; 

  //N.B. it doesn't help if I try specifying the parameters in Add() above

  wb.Save();
  wb.Close(m, m, m);

  //overkill to do GC so many times, but shows that doesn't fix it
  GC();
  //cleanup COM references
  //changing these all to FinalReleaseComObject doesn't help either
  while (Marshal.ReleaseComObject(wsnew) > 0) { } 
  wsnew = null;
  while (Marshal.ReleaseComObject(sheets) > 0) { }
  sheets = null;
  while (Marshal.ReleaseComObject(wb) > 0) { }
  wb = null;
  while (Marshal.ReleaseComObject(wbs) > 0) { }
  wbs = null;
  GC();
  excelapp.Quit();
  while (Marshal.ReleaseComObject(excelapp) > 0) { }
  excelapp = null;
  GC();
}

public static void GC()
{
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
}
هل كانت مفيدة؟

المحلول

ليس لدي الكود في متناول اليد، ولكن واجهت مشكلة مماثلة.إذا كنت أتذكر بشكل صحيح، انتهى بي الأمر باسترداد معرف العملية لمثيل Excel، وإيقافه (بعد فترة انتظار مناسبة، وعندما تفشل الطريقة الأخرى).

أعتقد أنني استخدمت:

GetWindowThreadProcessId (عبر P/Invoc) على خاصية hwnd لكائن Excel للحصول على معرف العملية، ثم استخدامه Process.GetProcessById للحصول على كائن العملية.بمجرد أن أفعل ذلك، سأتصل Kill على هذه العملية.

يحرر:يجب أن أعترف أن هذا ليس الحل الأمثل، ولكن إذا لم تتمكن من العثور على الواجهة المارقة التي لم يتم إصدارها، فسيؤدي ذلك إلى إصلاحها بطريقة قشر البيض/المطرقة الثقيلة.;)

تحرير 2:ليس عليك الاتصال Kill على كائن العملية على الفور ...يمكنك أولاً محاولة الاتصال Close قبل اللجوء إلى Kill.

نصائح أخرى

لقد فعلت شيئا مماثلا.أقوم بإنشاء ملف Excel أو فتح ملف موجود.أحذف جميع الأوراق وأضيف ورقتي الخاصة.إليك الكود الذي أستخدمه للتأكد من إغلاق جميع المراجع:

            workbook.Close(true, null, null);
            excelApp.Quit();

            if (newSheet != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(newSheet);
            }
            if (rangeSelection != null)
            {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(rangeSelection);
            }
            if (sheets != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets);
            }
            if (workbook != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
            }
            if (excelApp != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
            }

            newSheet = null;
            rangeSelection = null;
            sheets = null;
            workbook = null;
            excelApp = null;

            GC.Collect();

لقد اختبرت هذا مع العديد من الخيارات المختلفة ولم أفشل بعد.

إليك الكود الكامل الخاص بي لإيقاف برنامج Excel الذي قمت بإنشائه باستخدام مكتبة التشغيل المتداخل Office12 .Net:استمتع ، -لان.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Office.Interop.Excel;

class Program
{

    /// <summary> 
    /// Win32 API import for getting the process Id. 
    /// The out param is the param we are after. I have no idea what the return value is. 
    /// </summary> 
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId); 

    static void Main(string[] args)
    {
        var app = new Application();
        IntPtr hwnd = new IntPtr(app.Hwnd);
        IntPtr processId;
        IntPtr foo = GetWindowThreadProcessId(hwnd, out processId);
        Process proc = Process.GetProcessById(processId.ToInt32());
        proc.Kill(); // set breakpoint here and watch the Windows Task Manager kill this exact EXCEL.EXE
        app.Quit(); // should give you a "Sorry, I can't find this Excel session since you killed it" Exception.
    }
}

وهذا يعمل بشكل جيد جدا بالنسبة لي، دون أي استثناءات.

Public Class ExcelHlpr

    Declare Function EndTask Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal ShutDown As Boolean, ByVal Force As Boolean) As Integer

    Dim cXlApp As Microsoft.Office.Interop.Excel.Application

    Public Function GetExcel() As Microsoft.Office.Interop.Excel.Application
        cXlApp = New Microsoft.Office.Interop.Excel.Application
        Return cXlApp
    End Function

    Public Function EndExcel() As Integer
        Dim xlHwnd As New IntPtr(cXlApp.Hwnd)
        Return EndTask(xlHwnd, False, True)
    End Function

End Class

أعلم أن هذا ليس بناءًا للغاية، ولكنني اختبرت الكود تمامًا كما هو موضح أعلاه وخرجت عملية Excel الخاصة بي كما هو متوقع، ويوجد C:\addtest.xls الخاص بي مع 8 أوراق جديدة ولا توجد عملية Excel قيد التشغيل.
هل يمكن أن يكون إصدار التشغيل المتداخل هو السبب الذي أتساءل؟انا اختبرت 11 و 12

أنا أستخدم VB.NET 3.5 SP1 والتعليمة البرمجية التالية لا تزال تترك EXCEL.EXE مفتوحًا:

        xlWorkbook.Close(SaveChanges:=False)
        xlApplication.Quit()

        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorksheet)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheets)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkbook)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApplication)

        xlRange = Nothing
        xlWorksheet = Nothing
        xlSheets = Nothing
        xlWorkbook = Nothing
        xlApplication = Nothing

        GC.GetTotalMemory(False)
        GC.Collect()
        GC.WaitForPendingFinalizers()

        GC.Collect()
        GC.WaitForPendingFinalizers()
        GC.Collect()
        GC.GetTotalMemory(True)

أندرو، هذا هو الكود الذي وجدته يعمل.اعتقدت أنني أنشرها هنا للآخرين الذين يصادفون:

namespace WindowHandler
{
using System;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;

/// <summary>
/// Window class for handling window stuff.
/// This is really a hack and taken from Code Project and mutilated to this small thing.
/// </summary>
public class Window
{
    /// <summary>
    /// Win32 API import for getting the process Id.
    /// The out param is the param we are after. I have no idea what the return value is.
    /// </summary>
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId);

    /// <summary>
    /// Gets a Window's process Id.
    /// </summary>
    /// <param name="hWnd">Handle Id.</param>
    /// <returns>ID of the process.</returns>
    public static IntPtr GetWindowThreadProcessId(IntPtr hWnd)
    {
        IntPtr processId;
        IntPtr returnResult = GetWindowThreadProcessId(hWnd, out processId);

        return processId;
    }
}
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top