很久没更新了,感觉有点对不起dudu给的帐户,所以最近做了一点点.NET相关的事情,马上写出来各位分享。
最近的一个项目,需要将数据写入到Excel文件中。这本不是一个困难的事情,但是有一个问题始终没有解决,就是调用Excel对象后,系统中会有一个Excel进程没有退出。我使用的是VS2003。
google了相关的内容,很多人都遇到了同样的问题。解决的方法是调用System.Runtime.InteropServices.Marshal.ReleaseComObject方法,然后将引用的对象赋值为null,并执行垃圾回收。
官方的文档,请参考下面的链接。
使用 Visual Studio .NET 客户端执行自动化功能后不退出 Office 应用程序
经过实验,上面文章的说法是有一定效果的,文中的重点就是要使用对象类型进行操作,并且为每个要引用的对象创建变量,而且一定要在使用后释放。
在实际使用中,这种程序是很难写的,为了一段很少的功能,写了很长的一段代码,而且经常找不到造成引用未释放的语句。(有的时候,我定义一个int类型的变量都会造成Excel无法退出,真是气晕了)
搞了几天,也没有完全搞清楚这种程序如何写。所以想到网上通常使用的另外一种方法,就是KILL。
网络上提供的例子,通常是Kill系统内的Excel进程,这种方法容易出问题。研究了一下,做了一点改善。通过进程的命令行参数判断是否是OLE调用产生的Excel进程。这种方法也有问题,比如同时使用两个调用Excel的程序的时候,容易误杀。所以在实际使用的时候,还要多根据进程的信息进行判断。
下面是代码,供参考。(很少写代码,粗陋之处,莫见笑,仅供参考)
private void KillExcelProcess()
{
ProcessMemoryReader pReader = new ProcessMemoryReader();
//获得进程名未EXCEL的进程。
System.Diagnostics.Process[] myProcesses = System.Diagnostics.Process.GetProcessesByName("EXCEL");
//
if (myProcesses.Length == 0)
{
return;
}

//获取Excel进程的文件路径的长度。
byte [] byteTemp = System.Text.Encoding.ASCII.GetBytes(myProcesses[0].MainModule.FileName);
//这里+25是以为使用OLE方式调用的Excel进程,命令行信息会比正常的调用方式长25个字节。
int iPathLength = byteTemp.Length + 25;


for (int i=0;i<myProcesses.Length;i++)
{
//取得Process对象
pReader.ReadProcess = myProcesses[i];

//打开进程
pReader.OpenProcess();
int bytesReaded;
byte[] memoryAddr;

//获取GetCommandLine函数的地址
System.IntPtr GetCommandLineA_addr = ProcessMemoryAPI.GetProcAddress(ProcessMemoryAPI.GetModuleHandleA("Kernel32.dll"), "GetCommandLineA");
//函数地址的下一个地址,是要读取的内存的地址
GetCommandLineA_addr = (System.IntPtr)(GetCommandLineA_addr.ToInt32()+1);
int addr = System.Runtime.InteropServices.Marshal.ReadInt32(GetCommandLineA_addr);
//从内存中读地址信息
memoryAddr = pReader.ReadProcessMemory((IntPtr)addr,4,out bytesReaded);
addr = memoryAddr[0] + (memoryAddr[1]<<8) + (memoryAddr[2]<<16) + (memoryAddr[3]<<24);

//读命令行信息
byte[] memoryCmdLine;
memoryCmdLine = pReader.ReadProcessMemory((IntPtr)addr,(uint)iPathLength,out bytesReaded);
//
pReader.CloseHandle();

//转换为字符串
string strCmdLine = System.Text.Encoding.ASCII.GetString(memoryCmdLine,0,bytesReaded);
//判断Excel进程是否是OLE调用产生的进程。
if (strCmdLine.EndsWith("/automation -Embedding"))
{
//Kill the process.
myProcesses[i].Kill();
}
}
}


private void btnModify_Click(object sender, System.EventArgs e)
{

//定义需要使用的Excel对象
Excel.ApplicationClass xlsApp = new Excel.ApplicationClass(); // the Excel application.
Excel.Workbooks xlsBooks = null;
Excel.Workbook xlsBook = null;
Excel.Worksheet xlsSheet = null;

//设置Excel对象的属性
xlsApp.Visible = false;
xlsApp.ScreenUpdating = false;
xlsApp.DisplayAlerts = false;

//创建一个工作表
xlsBooks = xlsApp.Workbooks;
xlsBook = xlsBooks.Add(Missing.Value);
xlsSheet = (Excel.Worksheet)xlsApp.ActiveSheet;

//关闭Excel对象
if (xlsBook != null)
{
xlsBook.Close(true,Missing.Value,Missing.Value);
}

if(xlsApp != null)
{
xlsApp.Quit();
}

MessageBox.Show("如果此时系统中有残留的Excel进程,会被下面的函数结束。");

//结束Excel进程。
KillExcelProcess();

MessageBox.Show("请检查Excel进程是否结束。");
}
}

class ProcessMemoryAPI
{
//以下函数的功能,请参考MSDN中相对应的Windows API函数。
public const uint PROCESS_VM_READ = (0x0010);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

[DllImport("kernel32.dll")]
public static extern Int32 CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll")]
public static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,[In, Out] byte[] buffer, UInt32 size, out IntPtr lpNumberOfBytesRead);

[DllImport("kernel32.dll", SetLastError=true)]
public static extern System.IntPtr GetProcAddress ( int hModule, string lpProcName) ;

[DllImport("kernel32.dll", SetLastError=true)]
public static extern int GetModuleHandleA ( string lpModuleName) ;
}

public class ProcessMemoryReader
{

public ProcessMemoryReader()
{
}

public Process ReadProcess
{
get
{
return m_ReadProcess;
}
set
{
m_ReadProcess = value;
}
}

private Process m_ReadProcess = null;

private IntPtr m_hProcess = IntPtr.Zero;

//打开进程
public void OpenProcess()
{
m_hProcess = ProcessMemoryAPI.OpenProcess(ProcessMemoryAPI.PROCESS_VM_READ, 1, (uint)m_ReadProcess.Id);
}

//关闭进程
public void CloseHandle()
{
int iRetValue;
iRetValue = ProcessMemoryAPI.CloseHandle(m_hProcess);
if (iRetValue == 0)
throw new Exception("CloseHandle failed");
}

//读取进程中固定位置的内存数据
public byte[] ReadProcessMemory(IntPtr MemoryAddress, uint bytesToRead, out int bytesReaded)
{
byte[] buffer = new byte[bytesToRead];
IntPtr ptrBytesReaded;
ProcessMemoryAPI.ReadProcessMemory(m_hProcess,MemoryAddress,buffer ,bytesToRead,out ptrBytesReaded);
bytesReaded = ptrBytesReaded.ToInt32();

return buffer;
}
好像还有一种方法可以区别OLE调用的Excel进程和正常调用的Excel进程。OLE调用的Excel进程的父进程是svchost,正常Excel进程的父进程是explorer。没有编码测试过。
另外,编写Excel相关的程序,总是找不到比较完善的例子和说明,也可能是使用msdn这部分还不熟。这里分享一个小方法。Office的程序通常都提供宏录制的功能,如果需要找某些功能需要调用的接口和方法,可以通过录制的宏的代码来得到这部分信息。接口和对象都是一样。
最近的一个项目,需要将数据写入到Excel文件中。这本不是一个困难的事情,但是有一个问题始终没有解决,就是调用Excel对象后,系统中会有一个Excel进程没有退出。我使用的是VS2003。
google了相关的内容,很多人都遇到了同样的问题。解决的方法是调用System.Runtime.InteropServices.Marshal.ReleaseComObject方法,然后将引用的对象赋值为null,并执行垃圾回收。
官方的文档,请参考下面的链接。
使用 Visual Studio .NET 客户端执行自动化功能后不退出 Office 应用程序
经过实验,上面文章的说法是有一定效果的,文中的重点就是要使用对象类型进行操作,并且为每个要引用的对象创建变量,而且一定要在使用后释放。
在实际使用中,这种程序是很难写的,为了一段很少的功能,写了很长的一段代码,而且经常找不到造成引用未释放的语句。(有的时候,我定义一个int类型的变量都会造成Excel无法退出,真是气晕了)
搞了几天,也没有完全搞清楚这种程序如何写。所以想到网上通常使用的另外一种方法,就是KILL。
网络上提供的例子,通常是Kill系统内的Excel进程,这种方法容易出问题。研究了一下,做了一点改善。通过进程的命令行参数判断是否是OLE调用产生的Excel进程。这种方法也有问题,比如同时使用两个调用Excel的程序的时候,容易误杀。所以在实际使用的时候,还要多根据进程的信息进行判断。
下面是代码,供参考。(很少写代码,粗陋之处,莫见笑,仅供参考)






















































































































































































好像还有一种方法可以区别OLE调用的Excel进程和正常调用的Excel进程。OLE调用的Excel进程的父进程是svchost,正常Excel进程的父进程是explorer。没有编码测试过。
另外,编写Excel相关的程序,总是找不到比较完善的例子和说明,也可能是使用msdn这部分还不熟。这里分享一个小方法。Office的程序通常都提供宏录制的功能,如果需要找某些功能需要调用的接口和方法,可以通过录制的宏的代码来得到这部分信息。接口和对象都是一样。