Подтвердить что ты не робот

С# и автоматизация excel - завершение работы экземпляра

Я пытаюсь автоматизировать Excel через С#. Я выполнил все инструкции от Microsoft о том, как это сделать, но я все еще стараюсь отказаться от окончательной ссылки (ов) на Excel, чтобы она закрылась и чтобы GC мог ее собрать.

Далее следует образец кода. Когда я комментирую блок кода, содержащий строки, похожие на:

Sheet.Cells[iRowCount, 1] = data["fullname"].ToString();

тогда файл сохраняет и Excel завершает работу. В противном случае файл сохраняет, но Excel остается запущенным как процесс. В следующий раз, когда этот код запускается, он создает новый экземпляр, и в итоге он создается.

Любая помощь приветствуется. Спасибо.

Это баребоны моего кода:

        Excel.Application xl = null;
        Excel._Workbook wBook = null;
        Excel._Worksheet wSheet = null;
        Excel.Range range = null;

        object m_objOpt = System.Reflection.Missing.Value;

        try
        {
            // open the template
            xl = new Excel.Application();
            wBook = (Excel._Workbook)xl.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate, false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);
            wSheet = (Excel._Worksheet)wBook.ActiveSheet;

            int iRowCount = 2;

            // enumerate and drop the values straight into the Excel file
            while (data.Read())
            {

                wSheet.Cells[iRowCount, 1] = data["fullname"].ToString();
                wSheet.Cells[iRowCount, 2] = data["brand"].ToString();
                wSheet.Cells[iRowCount, 3] = data["agency"].ToString();
                wSheet.Cells[iRowCount, 4] = data["advertiser"].ToString();
                wSheet.Cells[iRowCount, 5] = data["product"].ToString();
                wSheet.Cells[iRowCount, 6] = data["comment"].ToString();
                wSheet.Cells[iRowCount, 7] = data["brief"].ToString();
                wSheet.Cells[iRowCount, 8] = data["responseDate"].ToString();
                wSheet.Cells[iRowCount, 9] = data["share"].ToString();
                wSheet.Cells[iRowCount, 10] = data["status"].ToString();
                wSheet.Cells[iRowCount, 11] = data["startDate"].ToString();
                wSheet.Cells[iRowCount, 12] = data["value"].ToString();

                iRowCount++;
            }

            DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\");
            _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate;
            wBook.Close(true, _report.ReportLocation, m_objOpt);
            wBook = null;

        }
        catch (Exception ex)
        {
            LogException.HandleException(ex);
        }
        finally
        {
            NAR(wSheet);
            if (wBook != null)
                wBook.Close(false, m_objOpt, m_objOpt);
            NAR(wBook);
            xl.Quit();
            NAR(xl);
            GC.Collect();
        }

private void NAR(object o)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
    }
    catch { }
    finally
    {
        o = null;
    }
}

Update

Независимо от того, что я пытаюсь использовать, "чистый" метод или "уродливый" метод (см. ответы ниже), экземпляр excel все еще висит вокруг, как только эта строка попадает:

wSheet.Cells[iRowCount, 1] = data["fullname"].ToString();

Если я прокомментирую эту строку (и другие подобные ей ниже), приложение Excel будет изящно завершено. Как только одна строка на приведенном выше расклассифицирована, Excel придерживается.

Я думаю, что мне нужно будет проверить, есть ли работающий экземпляр перед назначением переменной xl и привязка к ней. Я забыл упомянуть, что это служба Windows, но это не имеет значения, не так ли?


4b9b3361

Ответ 1

ОБНОВЛЕНИЕ (ноябрь 2016 г.)

Я только что прочитал убедительный аргумент от Hans Passant, что использование GC.Collect на самом деле является правильным путем. Я больше не работаю с Office (слава богу), но если бы я это сделал, я бы, вероятно, хотел бы попробовать эту другую попытку - это, несомненно, упростило бы многие из (тысяч строк) кода, который я написал, пытаясь сделать что-то "правильное" "(как я это видел).

Я оставлю свой оригинальный ответ для потомков...


Как говорит Майк в своем ответе, есть простой способ и трудный способ справиться с этим. Майк предлагает использовать простой способ, потому что... это проще. Я лично не считаю, что это достаточно хорошая причина, и я не верю этому правильно. Мне нравится "выключать и снова" для меня.

У меня есть несколько лет опыта разработки приложения автоматизации Office в .NET, и эти проблемы взаимодействия с COM сталкиваются со мной в течение первых нескольких недель и месяцев, когда я впервые столкнулся с проблемой, не в последнюю очередь потому, что Microsoft очень смущает нас проблема в первую очередь, и в то время хороших советов было трудно найти в Интернете.

У меня есть способ работы, который я сейчас использую практически, не задумываясь об этом, и это лет с тех пор, как у меня возникла проблема. По-прежнему важно быть живым для всех скрытых объектов, которые вы могли бы создать, и да, если вы пропустите один, у вас может быть утечка, которая будет проявляться гораздо позже. Но это не хуже того, что раньше было в плохие старые дни malloc/free.

Я действительно думаю, что есть что-то, что можно сказать о том, чтобы очистить себя, когда вы идете, а не в конце. Если вы только запустите Excel, чтобы заполнить несколько ячеек, то, возможно, это не имеет значения, но если вы собираетесь делать тяжелый подъем, то это другое дело.

В любом случае, метод, который я использую, заключается в использовании класса-оболочки, который реализует IDisposable, и который в своем методе Dispose вызывает ReleaseComObject. Таким образом, я могу использовать операторы using для обеспечения того, чтобы объект был удален (и объект COM освобожден), как только я закончил с ним.

Реально, он будет удален/выпущен, даже если моя функция вернется раньше или там исключение и т.д. Кроме того, он будет только удален или выпущен, если он был фактически создан в первую очередь - назовите меня педантом но предлагаемый код, который пытается освободить объекты, которые на самом деле не были созданы, выглядит мне как неаккуратный код. У меня есть аналогичное возражение против использования FinalReleaseComObject - вы должны знать, сколько раз вы вызывали создание ссылки на COM, и поэтому должны иметь возможность выпускать его столько же раз.

Типичный фрагмент моего кода может выглядеть так (или это было бы, если бы я использовал С# v2 и мог использовать generics: -)):

using (ComWrapper<Excel.Application> application = new ComWrapper<Excel.Application>(new Excel.Application()))
{
  try
  {
    using (ComWrapper<Excel.Workbooks> workbooks = new ComWrapper<Excel.Workbooks>(application.ComObject.Workbooks))
    {
      using (ComWrapper<Excel.Workbook> workbook = new ComWrapper<Excel.Workbook>(workbooks.ComObject.Open(...)))
      {
        using (ComWrapper<Excel.Worksheet> worksheet = new ComWrapper<Excel.Worksheet>(workbook.ComObject.ActiveSheet))
        {
          FillTheWorksheet(worksheet);
        }
        // Close the workbook here (see edit 2 below)
      }
    }
  }
  finally
  {
    application.ComObject.Quit();
  }
}

Теперь я не собираюсь делать вид, что это не многословие, и отступы, вызванные созданием объекта, могут выйти из-под контроля, если вы не разделите материал на более мелкие методы. Этот пример является чем-то наихудшим, поскольку все, что мы делаем, это создание объектов. Обычно между скобками и накладными расходами происходит гораздо больше.

Обратите внимание, что в соответствии с приведенным выше примером я всегда передавал бы "обернутые" объекты между методами, никогда не голый COM-объект, и ответственность за его обращение была бы вызвана (обычно с оператором using), Точно так же я всегда возвращал обернутый объект, никогда не голый, и снова это было бы причиной того, что вызывающий абонент выпустил его. Вы можете использовать другой протокол, но важно иметь четкие правила, так же, как это было, когда нам приходилось выполнять собственное управление памятью.

Класс ComWrapper<T>, используемый здесь, надеюсь, требует мало объяснений. Он просто хранит ссылку на обернутый COM-объект и освобождает его явно (используя ReleaseComObject) в своем методе Dispose. Метод ComObject просто возвращает типизированную ссылку на обернутый COM-объект.

Надеюсь, это поможет!

РЕДАКТИРОВАТЬ. Я только что перешел к ссылке Майку на другой вопрос, и я вижу, что другой ответ на этот вопрос вопрос имеет ссылку на класс обертки, как я предлагаю выше.

Кроме того, что касается ответа Майка на этот другой вопрос, я должен сказать, что меня почти соблазнил аргумент "just use GC.Collect". Однако в основном меня привлекало это на ложной предпосылке; на первый взгляд он выглядел так, как будто вообще не нужно было беспокоиться о ссылках на COM. Тем не менее, поскольку Майк говорит, что вам все еще нужно явно выпускать COM-объекты, связанные со всеми вашими переменными в области видимости, и поэтому все, что вы сделали, уменьшает, а не устраняет необходимость управления COM-объектами. Лично я предпочел бы погулять.

Я также отмечаю тенденцию во многих ответах на запись кода, где все будет выпущено в конце метода, в большом блоке вызовов ReleaseComObject. Это очень хорошо, если все работает так, как планировалось, но я хотел бы настоятельно призвать всех, кто писал серьезный код, рассмотреть, что произойдет, если будет выброшено исключение, или если у метода было несколько точек выхода (код не был бы выполнен, и, следовательно, COM-объекты не будет выпущен). Вот почему я предпочитаю использовать "обертки" и using s. Это многословие, но оно делает для пуленепробиваемого кода.

EDIT2: я обновил код выше, чтобы указать, где книга должна быть закрыта с сохранением или без сохранения изменений. Здесь код для сохранения изменений:

object saveChanges = Excel.XlSaveAction.xlSaveChanges;

workbook.ComObject.Close(saveChanges, Type.Missing, Type.Missing);

... и чтобы не сохранять изменения, просто измените xlSaveChanges на xlDoNotSaveChanges.

Ответ 2

Что происходит, так это ваш звонок:

Sheet.Cells[iRowCount, 1] = data["fullname"].ToString();

По существу, это то же самое, что:

Excel.Range cell = Sheet.Cells[iRowCount, 1];
cell.Value = data["fullname"].ToString();

Сделав это, вы увидите, что вы создаете объект Excel.Range, а затем присваиваете ему значение. Этот способ также дает нам именованную ссылку на нашу переменную диапазона, переменную cell, которая позволяет нам выпускать ее напрямую, если мы хотим. Таким образом, вы можете очистить объекты одним из двух способов:

(1) Трудный и уродливый способ:

while (data.Read())
{
    Excel.Range cell = Sheet.Cells[iRowCount, 1];
    cell.Value = data["fullname"].ToString();
    Marshal.FinalReleaseComObject(cell);

    cell = Sheet.Cells[iRowCount, 2];
    cell.Value = data["brand"].ToString();
    Marshal.FinalReleaseComObject(cell);

    cell = Sheet.Cells[iRowCount, 3];
    cell.Value = data["agency"].ToString();
    Marshal.FinalReleaseComObject(cell);

    // etc...
}

В вышеизложенном, мы отпускаем каждый объект диапазона через вызов Marshal.FinalReleaseComObject(cell) по мере продвижения.

(2) Легкий и чистый способ:

Оставьте свой код точно так, как вы его сейчас, а затем в конце вы можете очистить его следующим образом:

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

if (wSheet != null)
{
    Marshal.FinalReleaseComObject(wSheet)
}
if (wBook != null)
{
    wBook.Close(false, m_objOpt, m_objOpt);
    Marshal.FinalReleaseComObject(wBook);
}
xl.Quit();
Marshal.FinalReleaseComObject(xl);

Короче говоря, существующий код очень близок. Если вы просто добавляете вызовы к GC.Collect() и GC.WaitForPendingFinalizers() перед вашими вызовами NAR, я думаю, что это сработает для вас. (Короче говоря, и код Джейми, и код Ахмада верны. Джейми чище, но код Ахмада - это простое "быстрое исправление" для вас, потому что вам нужно будет только добавить вызовы к вызовам GC.Collect() и GC.WaitForPendingFinalizers() в ваш существующий код.)

Джейми и Амхад также перечислили ссылки на .NET Automation Forum, в котором я участвую (спасибо, ребята!) Вот пара связанных сообщения, которые я сделал здесь, на StackOverflow:

(1) Как правильно очистить объекты взаимодействия Microsoft в С#

(2) С# Автоматизация PowerPoint Excel - PowerPoint не выходит

Надеюсь, это поможет, Шон...

Mike

Ответ 3

Добавьте следующий код перед вызовом xl.Quit():

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

Вы также можете использовать Marshal.FinalReleaseComObject() в своем методе NAR вместо ReleaseComObject. ReleaseComObject уменьшает счетчик ссылок на 1, а FinalReleaseComObject освобождает все ссылки, поэтому счетчик равен 0.

Итак, ваш блок finally будет выглядеть следующим образом:

finally
{
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 

    NAR(wSheet);
    if (wBook != null)
        wBook.Close(false, m_objOpt, m_objOpt);
    NAR(wBook);
    xl.Quit();
    NAR(xl);
}

Обновленный метод NAR:

private void NAR(object o)
{
    try
    {
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(o);
    }
    catch { }
    finally
    {
        o = null;
    }
}

Я исследовал это некоторое время назад, и в примерах, которые я нашел, как правило, связанные с GC вызовы были завершены после закрытия приложения. Однако есть MVP (Майк Розенблюм), в котором упоминается, что его следует называть вначале. Я пробовал в обоих направлениях, и они работали. Я также пробовал его без WaitForPendingFinalizers, и он работал, хотя это ничего не должно повредить. YMMV.

Вот ссылки, которые я назвал MVP (они находятся в VB, но это не так):

Ответ 4

Как уже отмечалось ранее, InterOp предполагает, что если вы работаете с файлами Excel с расширением XLSX, вы должны использовать EPPlus, который будет ваши кошмары в Excel уходят.

Ответ 6

Свыше 4 лет с тех пор, как это было опубликовано, но я столкнулся с той же проблемой и смог ее решить. Очевидно, что просто доступ к массиву Cells создает COM-объект. Поэтому, если вам нужно:

    wSheet = (Excel._Worksheet)wBook.ActiveSheet;
    Microsoft.Office.Interop.Excel.Range cells = wSheet.Cells;

    int iRowCount = 2;

    // enumerate and drop the values straight into the Excel file
    while (data.Read())
    {
        Microsoft.Office.Interop.Excel.Range cell = cells[iRowCount, 1];
        cell  = data["fullname"].ToString();
        Marshal.FinalReleaseComObject(cell);
    }
    Marshal.FinalReleaseComObject(cells);

а затем остальную часть вашей очистки он должен устранить проблему.

Ответ 7

То, что я делал для решения подобной проблемы, это получить идентификатор процесса и убить его как последнее средство...

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

...

objApp = new Excel.Application();

IntPtr processID;
GetWindowThreadProcessId(objApp.Hwnd, out processID);
excel = Process.GetProcessById(processID.ToInt32());

...

objApp.Application.Quit();
Marshal.FinalReleaseComObject(objApp);
_excel.Kill();

Ответ 8

Здесь содержимое моего hasn't-fail-yet, наконец, блокирует очистку автоматизации Excel. Мое приложение оставляет Excel открытым, поэтому нет вызова Quit. ссылка в комментарии была моим источником.

finally
{
    // Cleanup -- See http://www.xtremevbtalk.com/showthread.php?t=160433
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();
    // Calls are needed to avoid memory leak
    Marshal.FinalReleaseComObject(sheet);
    Marshal.FinalReleaseComObject(book);
    Marshal.FinalReleaseComObject(excel);
}

Ответ 9

Считаете ли вы использование чистого .NET-решения, такого как SpreadsheetGear for.NET? Вот что вам может понравиться ваш код, например, с помощью SpreadsheetGear:

// open the template            
using (IWorkbookSet workbookSet = SpreadsheetGear.Factory.GetWorkbookSet())
{
    IWorkbook wBook = workbookSet.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate);
    IWorksheet wSheet = wBook.ActiveWorksheet;
    int iRowCount = 2;
    // enumerate and drop the values straight into the Excel file            
    while (data.Read())
    {
        wSheet.Cells[iRowCount, 1].Value = data["fullname"].ToString();
        wSheet.Cells[iRowCount, 2].Value = data["brand"].ToString();
        wSheet.Cells[iRowCount, 3].Value = data["agency"].ToString();
        wSheet.Cells[iRowCount, 4].Value = data["advertiser"].ToString();
        wSheet.Cells[iRowCount, 5].Value = data["product"].ToString();
        wSheet.Cells[iRowCount, 6].Value = data["comment"].ToString();
        wSheet.Cells[iRowCount, 7].Value = data["brief"].ToString();
        wSheet.Cells[iRowCount, 8].Value = data["responseDate"].ToString();
        wSheet.Cells[iRowCount, 9].Value = data["share"].ToString();
        wSheet.Cells[iRowCount, 10].Value = data["status"].ToString();
        wSheet.Cells[iRowCount, 11].Value = data["startDate"].ToString();
        wSheet.Cells[iRowCount, 12].Value = data["value"].ToString();
        iRowCount++;
    }
    DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\");
    _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate;
    wBook.SaveAs(_report.ReportLocation, FileFormat.OpenXMLWorkbook);
}

Если у вас больше нескольких строк, вы можете быть шокированы тем, насколько быстрее он работает. И вам никогда не придется беспокоиться о висячем экземпляре Excel.

Вы можете скачать бесплатную пробную версию здесь и попробовать ее для себя.

Отказ от ответственности: у меня есть SpreadsheetGear LLC

Ответ 10

Самый простой способ вставить код - это вопрос - это не значит, что я ответил на свой вопрос (к сожалению). Извиняюсь тем, кто пытается мне помочь - до сих пор я не мог вернуться к этому. У меня все еще есть... Я полностью изолировал код Excel в одной функции согласно

private bool GenerateDailyProposalsReport(ScheduledReport report)
{
    // start of test

    Excel.Application xl = null;
    Excel._Workbook wBook = null;
    Excel._Worksheet wSheet = null;
    Excel.Range xlrange = null;
    object m_objOpt = System.Reflection.Missing.Value;

    xl = new Excel.Application();
    wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);
    wSheet = (Excel._Worksheet)wBook.ActiveSheet;
    xlrange = wSheet.Cells[2, 1] as Excel.Range;

    // PROBLEM LINE ************
    xlrange.Value2 = "fullname";
    //**************************

    wBook.Close(true, @"c:\temp\DailyProposalReport.xls", m_objOpt);
    xl.Quit();

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

    Marshal.FinalReleaseComObject(xlrange);
    Marshal.FinalReleaseComObject(wSheet);
    Marshal.FinalReleaseComObject(wBook);
    Marshal.FinalReleaseComObject(xl);

    xlrange = null;
    wSheet = null;
    wBook = null;
    xl = null;

    // end of test
    return true;

}

Если я прокомментирую PROBLEM LINE выше, экземпляр Excel будет выпущен из памяти. В его нынешнем виде это не так. Я был бы признателен за дальнейшую помощь в этом, поскольку время мимолетно, и намечается крайний срок (не все).

Спросите, нужна ли вам дополнительная информация. Спасибо в ожидании.

Добавление

Немного больше информации, которая может или не может пролить свет на это. Я прибег к убийству процесса (мера остановки) после определенного промежутка времени (5-10 секунд, чтобы дать Excel время для его завершения). У меня запланировано два отчета - первый отчет создается и сохраняется на диске, а процесс Excel убит, а затем отправлен по электронной почте. Второй создается, сохраняется на диск, процесс уничтожается, но при ошибке электронной почты возникает ошибка. Ошибка:  Процесс не может получить доступ к файлу "...." и т.д.

Таким образом, даже когда приложение Excel было убито, фактический файл Excel по-прежнему удерживается службой Windows. Мне нужно убить службу, чтобы удалить файл...

Ответ 11

Я боюсь, что у меня заканчиваются идеи Шона.: - (

У Гэри могут быть какие-то мысли, но хотя его метод обертки очень прочный, на самом деле это не поможет вам в этом случае, потому что вы уже делаете все правильно.

Я приведу несколько соображений. Я не вижу, как любой из них будет работать, потому что ваша тайная линия

xlrange.Value2 = "fullname";

по-видимому, не повлияет ни одна из этих идей, но здесь говорится:

(1) Не используйте интерфейсы _Workbook и _Worksheet. Вместо этого используйте Workbook и Worksheet. (Подробнее об этом см.: Excel interop: _Worksheet или Worksheet?.)

(2) Каждый раз, когда вы получаете две точки ( "." ) в одной строке при доступе к объекту Excel, разбивайте его на две строки, присваивая каждому объекту именованную переменную. Затем в разделе очистки вашего кода явным образом выпустите каждую переменную, используя Marshal.FinalReleaseComObject().

Например, ваш код здесь:

 wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);

можно разбить на:

Excel.Workbooks wBooks = xl.Workbooks;
wBook = wBooks.Open("@"E:\Development\...\DailyProposalReport.xls", etc...);

И потом, в разделе очистки, у вас будет:

Marshal.FinalReleaseComObject(xlrange);
Marshal.FinalReleaseComObject(wSheet);
Marshal.FinalReleaseComObject(wBook);
Marshal.FinalReleaseComObject(wBooks); // <-- Added
Marshal.FinalReleaseComObject(xl);

(3) Я не уверен, что происходит с вашим подходом Process.Kill. Если вы вызываете wBook.Close(), а затем xl.Quit() перед вызовом Process.Kill(), у вас не должно быть проблем. Workbook.Close() не возвращает вам выполнение, пока рабочая книга не будет закрыта, а Excel.Quit() не вернет выполнение до тех пор, пока Excel не завершит закрытие (хотя он все равно может быть висит).

После вызова Process.Kill() вы можете проверить свойство Process.HasExited в цикле или, лучше, вызвать метод Process.WaitForExit(), который будет приостанавливаться до тех пор, пока он не выйдет за вас. Я бы предположил, что это, как правило, займет гораздо меньше секунды. Лучше подождать меньше времени и быть уверенным, чем ждать 5 - 10 секунд и только гадать.

(4) Вы должны попробовать эти идеи по очистке, перечисленные выше, но я начинаю подозревать, что у вас может быть проблема с другими процессами, которые могут работать с Excel, например надстройкой или антивирусом, вирусной программы. Эти надстройки могут привести к зависанию Excel, если они выполнены неправильно. Если это произойдет, может быть очень сложно или невозможно получить Excel для выпуска. Вам нужно будет выяснить программу нарушения, а затем отключить ее. Другая возможность заключается в том, что работа в качестве службы Windows как-то является проблемой. Я не понимаю, почему это было бы, но у меня нет опыта автоматизации Excel через Windows Service, поэтому я не могу сказать. Если ваши проблемы связаны с этим, то использование Process.Kill, скорее всего, будет вашим единственным курортом.

Это все, что я могу придумать из рук в руки, Шон. Надеюсь, это поможет. Сообщите нам, как это происходит...

- Майк

Ответ 12

Шон,

Я снова отправлю ваш код с моими изменениями (ниже). Я не слишком сильно менял свой код, поэтому я не добавил обработку исключений и т.д. Этот код не является надежным.

private bool GenerateDailyProposalsReport(ScheduledReport report)
{
    Excel.Application xl = null;
    Excel.Workbooks wBooks = null;
    Excel.Workbook wBook = null;
    Excel.Worksheet wSheet = null;
    Excel.Range xlrange = null;
    Excel.Range xlcell = null;

    xl = new Excel.Application();

    wBooks = xl.Workbooks;

    wBook = wBooks.Open(@"DailyProposalReport.xls", false, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

    wSheet = wBook.ActiveSheet;

    xlrange = wSheet.Cells;

    xlcell = xlrange[2, 1] as Excel.Range;

    xlcell.Value2 = "fullname";

    Marshal.ReleaseComObject(xlcell);
    Marshal.ReleaseComObject(xlrange);
    Marshal.ReleaseComObject(wSheet);

    wBook.Close(true, @"c:\temp\DailyProposalReport.xls", Type.Missing);

    Marshal.ReleaseComObject(wBook);
    Marshal.ReleaseComObject(wBooks);

    xl.Quit();

    Marshal.ReleaseComObject(xl);

    return true;
}

Примечание:

  • Метод Workbooks Application класс создает Workbooks, который содержит ссылка на соответствующий COM объекта, поэтому нам необходимо обеспечить, чтобы мы впоследствии освободить эту ссылку, поэтому я добавил переменную wBooks и соответствующий вызов ReleaseComObject.

  • Аналогично, метод Cells Worksheet возвращает объект Range объект с другим значением COM, поэтому нам тоже нужно это очистить. Отсюда требуется наличие двух отдельных переменных Range.

  • Я выпустил ссылки COM (используя ReleaseComObject), как только поскольку они больше не нужны, что я думаю, это хорошая практика, даже если это не является строго необходимым. Кроме того, (и это может быть суеверие) выпустил все объекты, принадлежащие книгу перед закрытием книгу и выпустила книгу перед закрытием Excel.

  • Я не звоню GC.Collect и т.д. потому что это не обязательно. Действительно!

  • Я использую ReleaseComObject скорее чем FinalReleaseComObject, потому что он должен быть вполне достаточным.

  • Я не обнуляю переменные после использование; еще раз, это не делает что-то стоящее.

  • Не имеет значения здесь, но я использую Type.Missing вместо System.Reflection.Missing.Value для удобство. Перейдите на С# v4, где дополнительные параметры будут поддерживается компилятором!

Я не смог скомпилировать или запустить этот код, но я уверен, что он сработает. Удачи!

Ответ 14

У меня была аналогичная проблема. Я удалил _worksheet и _workbook, и все было хорошо.