关于.NET程序中使用Excel对象后,如何结束残留的Excel进程。

很久没更新了,感觉有点对不起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)