Introduction
The whole idea of posting this article on Excel automation is to provide a set of Fn()s that would provide a better and cleaner way of releasing the memory. The article also contains other simple to use Fn()s which gives the developers ability to read and write (manipulate) excel data in a .NET based application project.
Motivation
The motivation to write this article came from a very basic problem, described on many forums that "Excel does not quit after automation" or "Office application does not quit after automation from Visual Studio .NET client". There are numerous articles which discuss this topic, so I thought of solving this with a simple and reusable solution.
Problem Statement
For effective management of memory in managed code environment, the CLR (Virtual Execution Engine) relays on garbage collector, but since the Runtime Callable Wrapper is itself a managed object, so lifetime of an Excel automation object is not guaranteed to end deterministically, as soon as we release reference to it. The Runtime Callable Wrapper is marked for garbage collection and is released when the GC wants to free more memory. This means that although the Excel application object is no longer used, still the application may remain loaded because the garbage collection has not yet freed the Runtime Callable Wrapper. For further reference on the problem statement, please feel free to go to Microsoft Knowledge Base: 317109.
Assumptions
The article assumes that the reader has basic understanding of the following:
- Object Oriented programming concepts
- Visual Studio .NET IDE
- C# based application development
- VSTO - Excel Automation
Using ExcelHelper class library
- Start Visual Studio 2008 .NET.
- Click on the File menu, click New and then click Project.
- Under Project types: Visual C#, select "Windows Forms Application" and click OK.
Form1
is created by default.
- Add a reference by Clicking on the menu "Project -> Add Reference".
- On the COM tab, locate "Microsoft Excel 9.0 Object Library" ver 1.3.0.0 and then click Select.
- Add a reference by Clicking on the menu "Project -> Add Reference".
- On the COM tab, locate "Microsoft Office 12.0 Object Library" ver 2.4.0.0 and then click Select.
- Add a reference by Clicking on the menu "Project -> Add Reference".
- On the .NET tab, locate "
Microsoft.Office.Interop.Excel
" ver 12.0.0.0 and then click Select.
- Add a reference by clicking on the menu "Project -> Add Existing Item".
- Then locate and Add ExcelHelper.cs file.
- Press (Cltr+w,x) to populate ToolBox and Add a button to the Form1.cs[Design] page.
- Double click on the button and add
OfficeAutomation
namespace to the Form1.cs file.
- Create an object of the
ExcelHelper
class and start excessing the functions as shown in the code snippet below:
void FunctionName()
{
ExcelHelper xlObj = new ExcelHelper("C:\\ExcelFile.xls");
xlObj.Open();
xlObj.Close();
xlObj.ReleaseObjects(); }
Mentioned below are the public
functions of the ExcelHelper
class that are used to perform operations on Excel file:
public ExcelHelper(string filePath) public ExcelHelper(string filePath, int workSheetNo) public ExcelHelper(string filePath, string workSheetName) public void Open()
public int ReadFromCellInteger(int rowId, int colId)
public int ReadFromCellInteger(object indexLoc)
public double ReadFromCellDouble(int rowId, int colId)
public double ReadFromCellDouble(object indexLoc)
public string ReadFromCellString(int rowId, int colId)
public string ReadFromCellString(object indexLoc)
public void WriteToCell(int rowId, int colId, int cellValue)
public void WriteToCell(int rowId, int colId, double cellValue)
public void WriteToCell(int rowId, int colId, string cellValue)
public void WriteToCell(int rowId, int colId, DateTime cellValue)
public void WriteToCells(object startIndexLoc, object endIndexLoc, int[,] cellValues)
public void WriteToCells(object startIndexLoc, object endIndexLoc, double[,] cellValues)
public void WriteToCells(object startIndexLoc, object endIndexLoc, string[,] cellValues)
public void WriteToCells(object startIndexLoc, object endIndexLoc, DateTime[,] cellValues)
public DateTime ReadFromCellDateTime(int rowId, int colId)
public DateTime ReadFromCellDateTime(object indexLoc)
public object ReadFromCells(object startIndexLoc, object endIndexLoc)
public void DataTableToExcel(int startRowId, int startColId,
System.Data.DataTable cellValues)
public string ConvertInteger2Alphabet(int colId)
public void Close()
public void ReleaseObjects()
Mentioned below are the code snippets of all the functions and how they can be used in the application project.
ExcelHelper xlObj = new ExcelHelper("C:\\ExcelFile.xls");
xlObj.Open();
xlObj.WriteToCell(3, 3, 1024);
int[,] saNames = new int[2, 2];
saNames[0, 0] = 1; saNames[0, 1] = 2;
saNames[1, 0] = 3; saNames[1, 1] = 4;
object obj_firstIndexLoc = "A7";
object obj_lastIndexLoc = "B8";
xlObj.WriteToCells(obj_firstIndexLoc, obj_lastIndexLoc, saNames);
int n_no = xlObj.ReadFromCellInteger(9, 2);
int n_no = xlObj.ReadFromCellInteger("I8");
double d_no = xlObj.ReadFromCellDouble(9, 2);
double d_no = xlObj.ReadFromCellDouble("C5");
string s_string = xlObj.ReadFromCellString(6, 5);
string s_string = xlObj.ReadFromCellString("D4");
DateTime dt = xlObj.ReadFromCellDateTime(4, 7);
DateTime dt = xlObj.ReadFromCellDateTime("G4");
object obj_values = xlObj.ReadFromCells("A7", "A9");
DataTable myDataTable = new DataTable();
xlObj.DataTableToExcel(3, 4, myDataTable);
string s_str = xlObj.ConvertInteger2Alphabet(987);
xlObj.Close();
xlObj.ReleaseObjects();
Approach Used to Clean Memory
In order to perform complete cleanup, a function named GetExcelProcessIdsSnapshot()
was created which takes a snapshot of all of the current Excel processes running in memory and then the same function would be called two times, once before opening the Excel file whose path is provided in the constructor and second time just after opening the Excel file and the results of both the Fn()
calls would be stored in separate Generics List and finally the GetExcelProcessId()
function would be passed the two Generics List and the alone standing Excel process (which was newly created as a result of the opening of Excel file) will be identified and later killed after releasing the Excel.WorkSheet
, Excel.WorkBook
and Excel.Application
object. Following is the code snippet of Open()
, GetExcelProcessIdsSnapshot()
and GetExcelProcessId()
functions.
public void Open()
{
try
{
l_initialProcessIds = this.GetExcelProcessIdsSnapshot();
xlAppObj = new Excel.ApplicationClass();
xlWorkBookObj = xlAppObj.Workbooks.Open(s_filePath, 0, false, 5,
System.Reflection.Missing.Value, System.Reflection.Missing.Value,
false, Excel.XlPlatform.xlWindows, "\t", false, false, 0, true);
if (n_workSheetNo != 0 && s_workSheetName == null)
{
xlWorkSheetObj =
(Excel.Worksheet)xlWorkBookObj.Worksheets.get_Item(n_workSheetNo);
l_finalProcessIds = this.GetExcelProcessIdsSnapshot();
n_processId = this.GetExcelProcessId(l_initialProcessIds, l_finalProcessIds);
}
if (s_workSheetName != null && n_workSheetNo == 0)
{
bool b_worksheetNameExist = true;
for (int n_loop = 1; n_loop <= xlWorkBookObj.Worksheets.Count; n_loop++)
{
xlWorkSheetObj =
(Excel.Worksheet)xlWorkBookObj.Worksheets.get_Item(n_loop);
if (xlWorkSheetObj.Name == s_workSheetName)
{
b_worksheetNameExist = true;
break;
}
b_worksheetNameExist = false;
}
if (b_worksheetNameExist == true)
{
l_finalProcessIds = this.GetExcelProcessIdsSnapshot();
n_processId = this.GetExcelProcessId
(l_initialProcessIds, l_finalProcessIds);
}
else
{
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error");
}
}
private List<int> GetExcelProcessIdsSnapshot()
{
List<int> ProcessIds = new List<int>();
try
{
Process[] Processes = Process.GetProcessesByName("Excel");
for (int n_loop = 0; n_loop < Processes.Count(); n_loop++)
ProcessIds.Add(Processes.ElementAt(n_loop).Id);
}
catch (Exception ex)
{
MessageBox.Show("Process Snapshot not Successful " + ex.ToString(), "Error");
}
return ProcessIds;
}
private int GetExcelProcessId(List<int> l_initialProcessIds, List<int> l_finalProcessIds)
{
try
{
for (int n_loop = 0; n_loop < l_initialProcessIds.Count; n_loop++)
{
int n_PidInitialProcessList = l_initialProcessIds.ElementAt(n_loop);
for (int n_innerloop = 0; n_innerloop < l_finalProcessIds.Count; n_innerloop++)
{
int n_PidFinalProcessList = l_finalProcessIds.ElementAt(n_innerloop);
if (n_PidInitialProcessList == n_PidFinalProcessList)
{
l_finalProcessIds.RemoveAt(n_innerloop);
break;
}
}
l_initialProcessIds.RemoveAt(n_loop);
n_loop--;
}
}
catch (Exception ex)
{
MessageBox.Show("GetExcelProcessId() unsuccessful" + ex.ToString(), "Error");
}
return l_finalProcessIds.ElementAt(0);
}
private void KillExcelProcessById(int n_processId)
{
try
{
Process xlProcess = null;
xlProcess = Process.GetProcessById(n_processId);
xlProcess.Kill();
}
catch (Exception ex)
{
MessageBox.Show("ProcessId " + n_processId.ToString() +
" could not be cleaned" + ex.ToString(), "Error");
}
}
Points of Interest
The basic idea of writing this class was to demonstrate the memory clean up technique, but the class has a lot of scope of improvement and there is no limit to the number of new functions that can be added for Excel manipulation ranging from password protection to Chart creation and so on...
History