View Single Post
  #22  
Old 07-13-2009, 12:56 PM
yoshijg yoshijg is offline
Newcomer
 
Join Date: Jun 2009
Posts: 7
Talking Solution to Killing Excel Process

I have a reliable solution, but it's in C#. I was searching for something to solve this problem, but there was no information out there. And I stumbled on to this forum, which had the most information. Anyway...I want to give, but it's in C#.

.....Ok....

Application.Quit() and Process.Kill() as suggested in previous posts are possible solutions, but have proven to be unreliable. When your main application dies, you are still left with Excel processes running. What we really want is for the Excel process to die as soon as the main application dies.

Well, after much reading...and a co-worker's suggestion. I came upon "job objects" http://msdn.microsoft.com/en-us/libr...09(VS.85).aspx .

The idea is to create a "job object" for your main application. Then configure the job object to kill-process upon close. If the application dies, the OS will take care of cleaning up your processes.

Code:
	public enum JobObjectInfoType
	{
		AssociateCompletionPortInformation = 7,
		BasicLimitInformation = 2,
		BasicUIRestrictions = 4,
		EndOfJobTimeInformation = 6,
		ExtendedLimitInformation = 9,
		SecurityLimitInformation = 5,
		GroupInformation = 11
	}

	[StructLayout(LayoutKind.Sequential)]
	public struct SECURITY_ATTRIBUTES
	{
		public int nLength;
		public IntPtr lpSecurityDescriptor;
		public int bInheritHandle;
	}

	[StructLayout(LayoutKind.Sequential)]
	struct JOBOBJECT_BASIC_LIMIT_INFORMATION
	{
		public Int64 PerProcessUserTimeLimit;
		public Int64 PerJobUserTimeLimit;
		public Int16 LimitFlags;
		public UInt32 MinimumWorkingSetSize;
		public UInt32 MaximumWorkingSetSize;
		public Int16 ActiveProcessLimit;
		public Int64 Affinity;
		public Int16 PriorityClass;
		public Int16 SchedulingClass;
	}

	[StructLayout(LayoutKind.Sequential)]
	struct IO_COUNTERS
	{
		public UInt64 ReadOperationCount;
		public UInt64 WriteOperationCount;
		public UInt64 OtherOperationCount;
		public UInt64 ReadTransferCount;
		public UInt64 WriteTransferCount;
		public UInt64 OtherTransferCount;
	}

	[StructLayout(LayoutKind.Sequential)]
	struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
	{
		public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
		public IO_COUNTERS IoInfo;
		public UInt32 ProcessMemoryLimit;
		public UInt32 JobMemoryLimit;
		public UInt32 PeakProcessMemoryUsed;
		public UInt32 PeakJobMemoryUsed;
	}

	public class Job : IDisposable
	{
		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
		static extern IntPtr CreateJobObject(object a, string lpName);

		[DllImport("kernel32.dll")]
		static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

		private IntPtr m_handle;
		private bool m_disposed = false;

		public Job()
		{
			m_handle = CreateJobObject(null, null);

			JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
			info.LimitFlags = 0x2000;

			JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
			extendedInfo.BasicLimitInformation = info;

			int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
			IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
			Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

			if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
				throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
		}

		#region IDisposable Members

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion

		private void Dispose(bool disposing)
		{
			if (m_disposed)
				return;

			if (disposing) {}

			Close();
			m_disposed = true;
		}

		public void Close()
		{
			Win32.CloseHandle(m_handle);
			m_handle = IntPtr.Zero;
		}

		public bool AddProcess(IntPtr handle)
		{
			return AssignProcessToJobObject(m_handle, handle);
		}

	}
Looking at the constructor ...

Code:
JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
info.LimitFlags = 0x2000;
The key here is to setup the job object properly. In the constructor I'm setting the "limits" to 0x2000, which is the numeric value for:

JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE

MSDN defines this flag as:

Quote:
Causes all processes associated with the job to terminate when the last handle to the job is closed.
Once this class is setup...you just have to register Excel process to the job. For example:

Code:
...

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

...


	Excel.Application app = new Excel.ApplicationClass();

	uint pid = 0;
	Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
	 job.AddProcess(Process.GetProcessById((int)pid).Handle);
Your "Job object" can be a static object, or whatever your desire. Note, we are basically letting the OS take care of our cleanup. So it's pretty much a guarantee that the Excel processes will be cleaned up after the main application process closes or CRASHES!!

- Josh
Reply With Quote