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

问题描述 投票:36回答:5

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

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

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

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

c# windows exception process
5个回答
22
投票

当您尝试访问MainModule属性时抛出异常。此属性的文档未列出Win32Exception作为可能的异常,但查看IL的属性很明显,访问它可能会抛出此异常。通常,如果您尝试执行操作系统中不可能或不允许的操作,则会抛出此异常。

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

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

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

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

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

无法枚举流程模块。

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


47
投票

请参阅Jeff Mercado的回答here

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

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

10
投票

如果你想摆脱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;
}

最后,让我们实现一个缓存,这样我们就不需要经常调用这个函数了。使用属性(1)进程名称(2)创建时创建一个ProcessCacheItem类。添加const ItemLifetime并设置为60秒。创建一个字典,其中key - process PID和value是ProcessCacheItem的对象实例。如果要获取进程名称,请先检入缓存。如果缓存中的项目已过期,请将其删除并添加刷新的项目。


2
投票

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


0
投票

也可以停用下一个选项...

] 1

© www.soinside.com 2019 - 2024. All rights reserved.