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

43

我开始了一个列出所有正在运行的进程的完整路径的新项目。当访问一些进程时,程序会崩溃并抛出一个Win32Exception。描述说在列出进程模块时发生了错误。最初我认为这个问题可能是因为我在一个64位平台上运行它,所以我将其重新编译为x86AnyCPU的CPU类型。然而,我仍然得到同样的错误。

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

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

有没有办法绕过这个错误消息?


没有看到代码很难确定,但我猜测可能是权限问题。 - M.Babcock
抱歉,我刚刚添加了相关代码。奇怪的是,它对大约70%的进程运行良好,但对一些进程不起作用。 - beta
不确定,您需要SeDebugPrivilege吗? - user541686
无论您尝试获取哪个进程的名称,它是否总是在尝试访问特定进程时发生? - M.Babcock
我不确定是否需要SeDebugPrivilege,但我认为不需要。 @M.Babcock:只有在尝试访问某些特定进程时才会发生这种情况。 - beta
5个回答

57

请参考Jeff Mercado的回答这里

我稍微修改了他的代码,只获取特定进程的文件路径:

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;
}

1
这是一个很好的解决方案,可以避免32/64位虚假无法访问消息的Win32Exception。 - Mike de Klerk
2
避免了问题,但速度非常慢。 - user562566
我所做的是将这个方法放入一个实用类中,然后在 try/catch 中包装尝试读取 process.MainModule.FileName 的过程,结果在 catch 中使用此方法。效果很好。但是,例如在防火墙应用程序中(在我的情况下),仅仅使用这个方法是不可能的,会严重拖慢用户体验。 - user562566
我相信这取决于一些环境条件,比如有多少进程正在运行,但你对该方法需要多长时间有什么测量结果?我刚刚测试了一下,在我的设置中得到了一个相当稳定的56-82毫秒。 - Mike Fuchs
我会为 ManagementObject 添加显式的 Dispose 调用或 using 语句,即使它没有必要,因为它有一个终结器。 - Andre Kampling
显示剩余2条评论

30
当您尝试访问MainModule属性时,会抛出异常。该属性的文档未将Win32Exception列为可能的异常,但查看该属性的IL代码可知,访问它可能会引发此异常。通常情况下,如果您尝试在操作系统中执行不可能或不允许的操作,将引发此异常。 Win32Exception有一个NativeErrorCode属性和一个Message属性,用于解释问题所在。您应该使用该信息来排除故障。 NativeErrorCode是Win32错误代码。我们可以猜测问题,但实际上找到问题的唯一方法是检查错误代码。
但继续猜测,这些异常的一个来源是从32位进程访问64位进程。这将抛出以下消息的Win32Exception

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

通过评估Environment.Is64BitProcess,您可以获取进程的位数。
即使作为64位进程运行,您也永远无法访问进程4(System)或进程0(System Idle Process)的MainModule。这将抛出以下带有消息的Win32Exception

无法枚举进程模块。

如果您的问题是要创建类似任务管理器中的进程列表,则必须以特殊方式处理0号和4号进程,并为它们指定特定名称(就像任务管理器一样)。请注意,在旧版本的Windows上,系统进程的ID是8。

非常感谢您澄清了这个行为。它主要是由“系统”和“空闲”进程处理的,因此我未能列出其主要模块。其他几个例外是一些服务,但我通过阅读shell命令'tasklist.exe'的输出找到了进程名称。现在可能是一个解决方法,但它运行良好。 - beta
我有点晚来到这个派对,但最近在我的应用程序运行时锁定计算机时遇到了这个问题。我的程序监视一个活动应用程序,我猜当您锁定计算机时,由于安全原因,该调用无法完成。不是100%确定,但想在此发表评论。 - Mungoid
可能会有另一个问题正在制作中,但从未来的角度来看,处理进程0和4的更清洁方法是否存在,而不是硬编码这些特定的PID?我总是可以捕捉异常,但如果可能的话,避免第一次出现异常似乎更好。 - Bryan

18

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

  1. 我们将使用Win32 API获取进程文件名
  2. 我们将实现一个缓存(仅解释)

首先,您需要导入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;
}

最后,让我们实现一个缓存,以便我们不必太频繁地调用此函数。创建一个名为 ProcessCacheItem 的类,其属性为(1)进程名称和(2)创建时间。添加常量 ItemLifetime 并设置为 60 秒。创建一个字典,其中键是进程 PID,值是 ProcessCacheItem 对象实例。当您想要获取进程名称时,请首先在缓存中检查。如果缓存项过期,则删除它并添加已刷新的项。


2
如果其他进程正在以提升的权限运行,这仍将失败。不要将0x0400 | 0x0010传递给OpenProcess,而是传递PROCESS_QUERY_LIMITED_INFORMATION(0x1000)。 - Dmitry Streblechenko

3
也可以停用下一个选项...
[项目属性] [Project Properties [1]

2

可能是因为您正在尝试访问某些进程(最有可能是在SYSTEM凭据下运行的进程)的MainModule属性,而您没有权限...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接