前几天当我还沉浸在学会怎样产生安全桌面并在里面放置登录事务的时候,在一次例行测试过程中我发现,如果在纯净版(使用官方镜像安装,没有打过补丁。之所以强调纯净版,是因为不排除微软会发布补丁修复这个问题的可能性)的Windows7上面生成安全桌面,会随之产生一个CtfMon.exe进程,但是当我们关闭了安全桌面的时候,这个进程不会被关闭,我测试了8次程序,进程列表里就出现了9个CtfMon(原本用户桌面上的那个当然也要算在内了),这怎么能行?要怎样解决这个问题?
直接Kill进程?
刚开始我的想法是:如果系统不自动关闭这个进程,那我就用程序把它给关了呗,于是就有了下面这段代码
1 2 3 |
var pros = System.Diagnostics.Process.GetProcesses(); foreach (var process in pros.Where(process => process.ProcessName.ToLower() == "ctfmon")) process.Kill(); |
我把这段代码插入到程序里面,当然,程序结束的时候CtfMon就不见了,可是随之而来的问题就是,我原桌面上的CtfMon也不见了,输入法都切换不了……这当然不行了。
那其他程序是怎么做的?
我一直在使用KeePass来作为我的密码管理工具,新版本的KeePass使用了c#作为编写语言,而它的登录窗口就使用到了安全桌面(前提是在设置里面启用了安全桌面登录),它是怎么做的呢?
于是我仔细观察了它的源代码,发现了下面这段话:
// Creating a window on the new desktop spawns a CtfMon.exe child
// process by default. On Windows Vista, this process is terminated
// correctly when the desktop is closed. However, on Windows 7 it
// isn’t terminated (probably a bug); creating multiple desktops
// accumulates CtfMon.exe child processes.
这段话大概的意思就是:默认状态下,在新桌面创建一个窗口会产生一个CtfMon.exe的子进程,在Vista下关闭桌面,这个进程也会随之结束,但是在Windows7下却不会(可能是个bug),会导致产生一大堆CtfMon子进程……
看来他们早就注意到这个问题了,他们自己写了一个叫做ChildProcessesSnapshot的类,它的大概原理就是在你初始化这个类的时候会产生一个进程快照,然后再调用他的TerminateNewChildsAsync,会将快照之后的进程结束掉,这样就避免了原本进程被K掉的情况。
于是我就把这个ChildProcessesSnapshot类搬来试试(没有直接完整复制过来,因为牵涉到了很多相关联的类,我稍稍整理了一下,原本的代码可以前往sourceforge参阅)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] internal struct PROCESSENTRY32 { public uint dwSize; public uint cntUsage; public uint th32ProcessID; public UIntPtr th32DefaultHeapID; public uint th32ModuleID; public uint cntThreads; public uint th32ParentProcessID; public int pcPriClassBase; public uint dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = ChildProcessesSnapshot.MAX_PATH)] public string szExeFile; } [Flags] internal enum ToolHelpFlags : uint { SnapHeapList = 0x00000001, SnapProcess = 0x00000002, SnapThread = 0x00000004, SnapModule = 0x00000008, SnapModule32 = 0x00000010, SnapAll = (SnapHeapList | SnapProcess | SnapThread | SnapModule), Inherit = 0x80000000U } public sealed class ChildProcessesSnapshot { [DllImport("Kernel32.dll")] internal static extern IntPtr CreateToolhelp32Snapshot( [MarshalAs(UnmanagedType.U4)] ToolHelpFlags dwFlags, uint th32ProcessID); [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe); [DllImport("Kernel32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe); [DllImport("Kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr hObject); internal const uint PROCESSENTRY32SizeUni32 = 556; internal const uint PROCESSENTRY32SizeUni64 = 568; internal const int MAX_PATH = 260; private readonly string m_strChildExeName; private readonly List<uint> m_lPids; public static bool IsInvalidHandleValue(IntPtr p) { long h = p.ToInt64(); if (h == -1) return true; if (h == 0xFFFFFFFF) return true; return false; } private static readonly char[] m_vDirSeps = new char[] { '\\', '/', LocalDirSepChar }; public static char LocalDirSepChar { get { return Path.DirectorySeparatorChar; } } public static string GetFileName(string strPath) { Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath"); int nLastSep = strPath.LastIndexOfAny(m_vDirSeps); if (nLastSep < 0) return strPath; if (nLastSep >= (strPath.Length - 1)) return string.Empty; return strPath.Substring(nLastSep + 1); } public ChildProcessesSnapshot(string strChildExeName) { m_strChildExeName = (string.IsNullOrEmpty(strChildExeName) ? string.Empty : GetExeName(strChildExeName)); m_lPids = GetChildPids(); } private List<uint> GetChildPids() { List<uint> lPids = new List<uint>(); try { uint pidThis = (uint)Process.GetCurrentProcess().Id; uint uEntrySize = (uint)Marshal.SizeOf(typeof( PROCESSENTRY32)); if (Environment.OSVersion.Version.Major >= 5) { int p = Marshal.SizeOf(typeof(IntPtr)); if (p == 4) { Debug.Assert(uEntrySize == PROCESSENTRY32SizeUni32); } else if (p == 8) { Debug.Assert(uEntrySize == PROCESSENTRY32SizeUni64); } } IntPtr hSnap = CreateToolhelp32Snapshot( ToolHelpFlags.SnapProcess, 0); if (IsInvalidHandleValue(hSnap)) { Debug.Assert(false); return lPids; } for (int i = 0; i < int.MaxValue; ++i) { PROCESSENTRY32 pe = new PROCESSENTRY32(); pe.dwSize = uEntrySize; bool b; if (i == 0) b = Process32First(hSnap, ref pe); else b = Process32Next(hSnap, ref pe); if (!b) break; if (pe.th32ProcessID == pidThis) continue; if (pe.th32ParentProcessID != pidThis) continue; if (!string.IsNullOrEmpty(m_strChildExeName)) { if (pe.szExeFile == null) { Debug.Assert(false); continue; } string str = GetExeName(pe.szExeFile); if (!str.Equals(m_strChildExeName, StringComparison.OrdinalIgnoreCase)) continue; } lPids.Add(pe.th32ProcessID); } if (!CloseHandle(hSnap)) { Debug.Assert(false); } } catch (Exception) { Debug.Assert(false); } return lPids; } private static char[] m_vTrimChars = null; private static string GetExeName(string strPath) { if (strPath == null) { Debug.Assert(false); return string.Empty; } if (m_vTrimChars == null) m_vTrimChars = new char[] { '\r', '\n', ' ', '\t', '\"', '\'' }; string str = strPath.Trim(m_vTrimChars); str = GetFileName(str); return str; } public void TerminateNewChildsAsync(int nDelayMs) { List<uint> lPids = GetChildPids(); foreach (uint uPid in lPids) { if (m_lPids.IndexOf(uPid) < 0) { CpsTermInfo ti = new CpsTermInfo(uPid, m_strChildExeName, nDelayMs); ParameterizedThreadStart pts = new ParameterizedThreadStart( ChildProcessesSnapshot.DelayedTerminatePid); Thread th = new Thread(pts); th.Start(ti); } } } private sealed class CpsTermInfo { private readonly uint m_uPid; public uint ProcessId { get { return m_uPid; } } private readonly string m_strExeName; public string ExeName { get { return m_strExeName; } } private readonly int m_nDelayMs; public int Delay { get { return m_nDelayMs; } } public CpsTermInfo(uint uPid, string strExeName, int nDelayMs) { m_uPid = uPid; m_strExeName = strExeName; m_nDelayMs = nDelayMs; } } private static void DelayedTerminatePid(object oTermInfo) { try { CpsTermInfo ti = (oTermInfo as CpsTermInfo); if (ti == null) { Debug.Assert(false); return; } if (ti.Delay > 0) Thread.Sleep(ti.Delay); Process p = Process.GetProcessById((int)ti.ProcessId); if (p == null) { Debug.Assert(false); return; } if (!string.IsNullOrEmpty(ti.ExeName)) { string str = GetExeName(p.MainModule.FileName); if (!str.Equals(ti.ExeName, StringComparison.OrdinalIgnoreCase)) { Debug.Assert(false); return; } } p.Kill(); p.Close(); } catch (ArgumentException) { } catch (Exception) { Debug.Assert(false); } } } |
使用方法很简单:
1 2 3 4 5 |
//在打开安全桌面之前产生进程快照 ChildProcessesSnapshot cpsCtfMons = new ChildProcessesSnapshot("CtfMon.exe"); //关闭安全桌面以后杀掉产生的进程 cpsCtfMons.TerminateNewChildsAsync(2000); |
经测试果然比较奏效(再次佩服一下这些大牛的思考能力)。
当然了,这个类肯定还可以衍生出很多用法,灵活运用嘛…
当然,记得在分享的时候别删掉他们的版权信息,这也算是对原作者的一种尊重。
1条评论. Leave new
[…] PS:我在使用的过程中发现在某些Win7系统中(Win8.1和Win10经测试没有发现这个问题),打开一次桌面就会打开一个输入法管理器进程(CtfMon.exe),而桌面关闭了以后它不会关闭,这样可能会出现进程中有好几个甚至N多CtfMon,要怎么解决这个问题呢?这个我们就到这篇文章里面去讨论吧。 […]