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

bet*_*eta 36 c# windows exception process

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

Process p = Process.GetProcessById(2011);
string s = proc_by_id.MainModule.FileName;
Run Code Online (Sandbox Code Playgroud)

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

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

Mik*_*chs 49

在此处查看Jeff Mercado的回答.

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

string s = GetMainModuleFilepath(2011);
Run Code Online (Sandbox Code Playgroud)

.

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 可以很好地避免这个问题,但速度很慢。 (2认同)

Mar*_*age 22

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

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

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

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

您可以通过评估获得流程的位数Environment.Is64BitProcess.

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

无法枚举流程模块.

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


小智 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);
Run Code Online (Sandbox Code Playgroud)

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

[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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 如果另一个进程以提升的权限运行,这仍然会失败。而不是传递 0x0400 | 0x0010 到 OpenProcess,传递 PROCESS_QUERY_LIMITED_INFORMATION (0x1000) (2认同)