在 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 类型 x86 和 AnyCPU 重新编译了它强>。不过,我遇到了同样的错误。
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
具有属性NativeErrorCode
和Message
,可以解释问题所在。您应该使用该信息来解决您的问题。 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 异常?的主要内容,如果未能解决你的问题,请参考以下文章