在C#中访问Process.MainModule.FileName时如何避免Win32exception?

我开始了一个新的项目,列出所有正在运行的进程的完整path。 当访问一些进程时,程序崩溃并抛出一个Win32Exception 。 说明中列出了列出stream程模块时发生的错误。 最初我以为这个问题可能会发生,因为我在64位平台上运行它,所以我重新编译了CPUtypesx86AnyCPU 。 不过,我也遇到了同样的错误。

Process p = Process.GetProcessById(2011); string s = proc_by_id.MainModule.FileName; 

错误发生在第2行。 空白字段显示出现错误的进程: 截图

有没有办法解决这个错误信息?

当您尝试访问MainModule属性时引发exception。 该属性的文档没有列出Win32Exception作为可能的exception,但是查看IL的属性显然访问它可能会引发此exception。 一般来说,如果你正在尝试做一些在操作系统中是不可能的或者不允许的事情,它会抛出这个exception。

Win32Exception具有NativeErrorCode属性,也是一个Message ,将解释什么是问题。 您应该使用该信息来解决您的问题。 NativeErrorCode是Win32错误代码。 我们可以整天猜测问题是什么,但实际解决这个问题的唯一方法是检查错误代码。

但是要继续猜测,这些exception的一个来源是从32位进程访问64位进程。 这样做会抛出一个Win32Exception以下消息:

32位进程无法访问64位进程的模块。

您可以通过评估Environment.Is64BitProcess来获取进程的位数。

即使作为64位进程运行,也绝不允许访问进程4(系统)或进程0(系统空闲进程)的MainModule 。 这将抛出一个Win32Exception与消息:

无法枚举进程模块。

如果你的问题是你想要一个类似于任务pipe理器的进程列表,你将不得不以特殊的方式处理进程0和4,并给他们特定的名字(就像任务pipe理器一样)。 请注意,在旧版本的Windows上,系统进程ID为8。

请看这里的杰夫·梅尔卡多的答案。

我稍微调整了自己的代码以获取特定进程的文件path:

 string s = GetMainModuleFilepath(2011); 

 private string GetMainModuleFilepath(int processId) { string wmiQueryString = "SELECT ProcessId, ExecutablePath FROM Win32_Process WHERE ProcessId = " + processId; using (var searcher = new ManagementObjectSearcher(wmiQueryString)) { using (var results = searcher.Get()) { ManagementObject mo = results.Cast<ManagementObject>().FirstOrDefault(); if (mo != null) { return (string)mo["ExecutablePath"]; } } } return null; } 

如果你想摆脱Win32Exception,并获得最佳性能,让我们这样做:

  1. 我们将使用Win32 API来获取进程文件名
  2. 我们将实现一个caching(只有解释)

首先,您需要导入Win32 API

 [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId); [DllImport("psapi.dll")] static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); 

其次,我们来编写返回进程文件名的函数。

 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetProcessName(int pid) { var processHandle = OpenProcess(0x0400 | 0x0010, false, pid); if (processHandle == IntPtr.Zero) { return null; } const int lengthSb = 4000; var sb = new StringBuilder(lengthSb); string result = null; if (GetModuleFileNameEx(processHandle, IntPtr.Zero, sb, lengthSb) > 0) { result = Path.GetFileName(sb.ToString()); } CloseHandle(processHandle); return result; } 

最后,让我们实现一个caching,所以我们不需要经常调用这个函数。 用属性创build一个类ProcessCacheItem(1)进程名(2)创build时间。 添加一个const ItemLifetime并设置为60秒。 创build一个字典,其中键 – 进程PID和值是ProcessCacheItem的对象实例。 当你想获取进程名时,首先检查caching。 如果caching中的项目过期,请将其删除并添加刷新的项目。

也许是因为您正在尝试访问您没有权限的某些进程(很可能是在SYSTEM凭据下运行的那些进程)的MainModule属性…