Как избежать исключения Win32 при доступе к Process.MainModule.FileName в С#?

Я начал новый проект, перечисляя полные пути для всех запущенных процессов. При доступе к некоторым процессам программа вылетает и бросает Win32Exception. В описании указано, что во время перечисления модулей процесса произошла ошибка. Первоначально я думал, что эта проблема может возникнуть, потому что я запускаю ее на платформе 64-разрядной, поэтому я перекомпилировал ее для типов процессоров x86 и AnyCPU strong > . Однако я получаю ту же ошибку.

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

Ошибка в строке # 2. Пустые поля показывают процессы, в которых произошла ошибка: Screenshot

Есть ли способ обойти это сообщение об ошибке?

Ответ 1

Исключение возникает при попытке получить доступ к свойству MainModule. Документация для этого свойства не отображает Win32Exception как возможное исключение, но, глядя на IL для свойства, очевидно, что доступ к нему может вызвать это исключение. В общем случае это будет исключение, если вы пытаетесь сделать что-то, что невозможно или не разрешено в ОС.

Win32Exception имеет свойство NativeErrorCode, а также a Message, которое объяснит, в чем проблема. Вы должны использовать эту информацию для устранения неполадок. NativeErrorCode - код ошибки Win32. Мы можем угадать весь день, что проблема, но единственный способ понять это - проверить код ошибки.

Но для продолжения угадывания одним из источников этих исключений является доступ к 64-битным процессам из 32-битного процесса. Выполнение этого вызовет Win32Exception со следующим сообщением:

32-битные процессы не могут получить доступ к модулям 64-битного процесса.

Вы можете получить количество бит вашего процесса, оценив Environment.Is64BitProcess.

Даже работая как 64-битный процесс, вам никогда не будет разрешен доступ к MainModule процесса 4 (система) или процесса 0 (процесс ожидания системы). Это вызовет сообщение Win32Exception с сообщением:

Невозможно перечислить модули процесса.

Если проблема заключается в том, что вы хотите сделать список процессов, аналогичный такому в Диспетчере задач, вам придется обрабатывать процессы 0 и 4 особым образом и давать им конкретные имена (как это делает диспетчер задач). Обратите внимание, что в старых версиях Windows системный процесс имеет ID 8.

Ответ 2

Пожалуйста, см. 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;
}

Ответ 3

Если вы хотите избавиться от Win32Exception и получить лучшую производительность, сделайте следующее:

  • Мы будем использовать Win32 API для получения имени файла процесса
  • Мы реализуем кеш (только объяснение)

Сначала вам нужно импортировать API Win32

[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. Когда вы хотите получить имя процесса, сначала проверьте кеш. Если элемент в кеше истек, удалите его и добавьте обновленный.

Ответ 4

Возможно, потому, что вы пытаетесь получить доступ к свойству MainModule для некоторых процессов (скорее всего, те, которые работают под SYSTEM), на которых у вас нет разрешения...