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

Posted

技术标签:

【中文标题】在 C# 中访问 Process.MainModule.FileName 时如何避免 Win32 异常?【英文标题】:How to avoid a Win32 exception when accessing Process.MainModule.FileName in C#? 【发布时间】:2012-03-19 02:17:20 【问题描述】:

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

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

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

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

【问题讨论】:

如果没有看到代码,很难确定,但我猜这是权限问题。 很抱歉。我刚刚添加了相关代码。奇怪的是,它在大约 70% 的进程中运行良好,但在少数进程中却不行。 不确定,是否需要 SeDebugPrivilege? 在尝试访问特定进程时是否总是发生这种情况,或者无论您尝试获取哪个进程的名称都会发生这种情况? 我不确定是否需要 SeDebugPrivilege,但我不这么认为。 @M.Babcock:仅在尝试访问某些特定进程时确实会发生这种情况。 【参考方案1】:

请参阅 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;

【讨论】:

这看起来也是一个很好的解决方案,虽然我前段时间已经解决了。无论如何,谢谢! 这是一个很好的解决方案,可以避免32/64位伪造无法访问消息的Win32Exception。 根据我的经验,为了返回 SYSTEM 进程,我必须以管理员身份运行可执行文件。 可以很好地避免这个问题,但速度很慢。 我所做的是将这个方法放入一个实用程序类中,然后将读取 process.MainModule.FileName 的尝试包装在 try/catch 中,从而在 catch 中使用这个方法。效果很好。但是,试图在像防火墙应用程序这样苛刻的应用程序(在我的情况下)中使用这种方法是不可能的。过度拖累用户体验。【参考方案2】:

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

Win32Exception 具有属性NativeErrorCodeMessage,可以解释问题所在。您应该使用该信息来解决您的问题。 NativeErrorCode 是 Win32 错误代码。我们可以整天猜测问题是什么,但真正弄清楚这一点的唯一方法是检查错误代码。

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

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

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

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

无法枚举进程模块。

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

【讨论】:

非常感谢您澄清这种行为。它主要是“系统”和“空闲”的进程,因此我未能列出其主要模块。其他少数例外是一些服务,但我设法通过读取 shell 命令“tasklist.exe”的输出找到了进程名称。目前可能是一种解决方案,但效果很好。 我参加这个聚会有点晚了,但我最近在我的应用程序运行时锁定我的电脑时遇到了这个问题。我的 prog 观看了一个活动的应用程序,出于安全原因,我猜测当您锁定计算机时,该呼叫无法完成。对此不是 100% 确定,但想通了 id 评论 可能是另一个正在酝酿中的问题,但从未来证明的角度来看,是否有一种更清洁的方式来处理进程 0 和 4,而不是对这些特定的 PID 进行硬编码?我总是可以捕捉到异常,但如果可能的话,最好首先避免异常。【参考方案3】:

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

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

首先需要导入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 秒。创建一个字典,其中键 - 进程 PID 和值是 ProcessCacheItem 的对象实例。当您想获取进程名称时,首先检查缓存。如果缓存中的项已过期,请将其删除并添加刷新的项。

【讨论】:

如果其他进程以提升的权限运行,这仍然会失败。而不是通过 0x0400 | 0x0010 到 OpenProcess,传递 PROCESS_QUERY_LIMITED_INFORMATION (0x1000)【参考方案4】:

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

]1

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于在 C# 中访问 Process.MainModule.FileName 时如何避免 Win32 异常?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 问题中访问受密码保护的 MS 访问数据库

如何在 C# 中访问 RTMPTE 流

在 C# 中访问路径被拒绝错误

如何在 C# 代码中访问 C++ 类?

如何在 C# 中访问受保护的方法

在访问数据库c#中插入日期和时间