[dot Net] 죽지 않는 엑셀 프로세스 죽이기


엑셀 내보내기를 .net에서 C#으로 처음으로 구현해봤습니다.

엑셀내보내기

if (Grid.Rows.Count > 0)
{
    Microsoft.Office.Interop.Excel.Application excel = null;
    Workbook workbook = null;
    Worksheet worksheet = null;

    bool IsSuccess = false;
    try
    {
        string SavePath = @"c:\저장경로\";
        Directory.CreateDirectory(SavePath);
        string PathFilename = SavePath + strFilename + @".xlsx";

        excel = new Microsoft.Office.Interop.Excel.Application();
        workbook = excel.Workbooks.Add(System.Reflection.Missing.Value);
        worksheet = workbook.Worksheets.Item["Sheet1"];
        excel.DisplayAlerts = false;

        //column width
        Range ModRange = worksheet.Columns[1];
        ModRange.ColumnWidth = 4;
        ModRange = worksheet.Columns[2];
        ModRange.ColumnWidth = 4;
        ModRange = worksheet.Columns[3];
        ModRange.ColumnWidth = 18;
        ModRange.NumberFormat = "@";

        //title
        ModRange = (Range)worksheet.get_Range("A1", "W1");
        ModRange.Merge(true);
        ModRange.Value = "Title";
        ModRange.Font.Size = 16;
        ModRange.Font.Bold = true;
        ModRange.HorizontalAlignment = XlHAlign.xlHAlignCenter;
        ModRange.BorderAround2(XlLineStyle.xlContinuous, XlBorderWeight.xlMedium, XlColorIndex.xlColorIndexAutomatic, XlColorIndex.xlColorIndexAutomatic);

        //report time
        ModRange = (Range)worksheet.get_Range("A2", "W2");
        ModRange.Merge(true);
        ModRange.Value = "Report Date: " + DateTime.Now.ToString();
        ModRange.HorizontalAlignment = XlHAlign.xlHAlignRight;

        //head
        for (int i = 0; i < Grid.Columns.Count; i++)
        {
            ModRange = (Range)worksheet.Cells[3, 1 + i];
            ModRange.Value = Grid.Columns[i].HeaderText;
            ModRange.HorizontalAlignment = XlHAlign.xlHAlignCenter;

            //data 테두리
            ModRange.BorderAround2(XlLineStyle.xlContinuous, XlBorderWeight.xlThin, XlColorIndex.xlColorIndexAutomatic, XlColorIndex.xlColorIndexAutomatic);
            ModRange.Borders[XlBordersIndex.xlEdgeTop].Weight = XlBorderWeight.xlMedium;
            if (i == 0)
            {
                ModRange.Borders[XlBordersIndex.xlEdgeLeft].Weight = XlBorderWeight.xlMedium;
            }
            else if (i == (Grid.Columns.Count - 1))
            {
                ModRange.Borders[XlBordersIndex.xlEdgeRight].Weight = XlBorderWeight.xlMedium;
            }
            ModRange.Borders[XlBordersIndex.xlEdgeBottom].LineStyle = XlLineStyle.xlDouble;
            ModRange.Borders[XlBordersIndex.xlEdgeBottom].Weight = XlBorderWeight.xlThick;
            //////
        }

        //data
        for (int i = 0; i < Grid.Rows.Count; i++)
        {
            for (int j = 0; j < Grid.Columns.Count; j++)
            {
                ModRange = (Range)worksheet.Cells[4 + i, 1 + j];
                ModRange.Value = Grid[j, i].Value == null ? "" : Grid[j, i].Value.ToString();

                //data 테두리
                ModRange.BorderAround2(XlLineStyle.xlContinuous, XlBorderWeight.xlThin, XlColorIndex.xlColorIndexAutomatic, XlColorIndex.xlColorIndexAutomatic);
                if (j == 0)
                {
                    ModRange.Borders[XlBordersIndex.xlEdgeLeft].Weight = XlBorderWeight.xlMedium;
                }
                else if (j == (Grid.Columns.Count - 1))
                {
                    ModRange.Borders[XlBordersIndex.xlEdgeRight].Weight = XlBorderWeight.xlMedium;
                }
                if (i == (Grid.Rows.Count - 1))
                {
                    ModRange.Borders[XlBordersIndex.xlEdgeBottom].Weight = XlBorderWeight.xlMedium;
                }
                //////
            }
        }
        workbook.SaveAs(Filename: PathFilename);
        workbook.Close();
        excel.Quit();
        IsSuccess = true;
    }
    catch (Exception ex)
    {
        IsSuccess = false;
        MessageBox.Show("저장에 실패하였습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        Console.WriteLine(ex.ToString());
    }

    if (IsSuccess)
    {
        MessageBox.Show("저장 완료", "정보", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}
else
{
    MessageBox.Show("데이터가 없습니다.\n 검색을 먼저 실행해주십시오.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

원래 소스에서는 명칭도 다르고 메인 폼의 상태 바에서 프로그레스로 진행률 표시한다고

부분적으로 좀 다르기는 한데 엑셀 파일 내보내기 부분만 보면 위와 같습니다.


약간의 노가다(?)를 요하는 테두리 작업까지 완료되어

전체적으로 쉽게 쓸 수 있게 잘 만들어져있다고 생각했습니다.


꾸미기 부분이 반이 넘을 뿐 실제로 소스는 단순합니다.




버튼종류


그러나…

이대로는 내보내기 횟수 만큼 엑셀 프로세스가 증가합니다.


PC를 재부팅하니 테스트 한 수만큼의 무수히 많은 엑셀파일이 열렸습니다.

아시다시피 윈도우10은 실행중인 프로세스를 재시작 시 이어서 실행합니다.

PC가 종료 될 때 그 개수만큼 프로세스가 남아 있었던 겁니다.

프로세스를 이어서 재시작하지 않았으면 엑셀 프로세스가 죽지 않는 걸 모를 뻔했습니다!


라이브러리에서 종료하는 부분이 떡하니 들어가 있어서

릴리즈 될 것을 기대했는데 죽지 않는다니.. .net을 욕했습니다.


그래서 Excel 이름의 프로세스를 죽이자니

사용자가 엑셀을 사용하고 있을 경우를 생각해야 하여

어떻게 할지 ‘c# excel process not closing‘으로 구글링하니

역시나 여러 글들을 발견할 수 있습니다.

이렇게 죽지 않는 엑셀 프로세스를 죽이는 언데드 소탕을 시작했습니다.


FinalReleaseComObject를 사용하라는 여러 글들이 많았는데

몇 가지 구현 방법을 따라하고 만들었으나

구현 방법의 문제인지 테스트 결과 신뢰도가 떨어져


사용자가 엑셀을 사용하고 있을 경우를 대비하여

생성할 때 만든 엑셀 프로세스의 핸들의 프로세스 아이디(PID)를 받아

만들어진 엑셀 프로세스를 소탕하였습니다.

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
workbook.SaveAs(Filename: PathFilename);
workbook.Close();
uint processId = 0;
GetWindowThreadProcessId(new IntPtr(excel.Hwnd), out processId);
excel.Quit();
if (processId != 0)
{
    System.Diagnostics.Process excelProcess = System.Diagnostics.Process.GetProcessById((int)processId);
    excelProcess.CloseMainWindow();
    excelProcess.Refresh();
    excelProcess.Kill();
}


References