Быстрый способ получить владельцев процессов в .NET

#.net #winapi #pinvoke

Вопрос:

У меня есть следующее .ЧИСТЫЙ код, который обращается к OpenProcessToken Win32 API для получения имен владельцев всех процессов в системе:

 using System.Security.Principal;
using System.Runtime.InteropServices;

public class Test
{
    [DllImport("advapi32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

    [DllImport("kernel32.dll", SetLastError=true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);
    
    private const UInt32 TOKEN_QUERY = 0x0008;

    public static List<List<String>> GetProcessWithUsers() 
    {
        var processes = Process.GetProcesses();
        var result = new List<List<string>>();
        foreach (var proc in processes)
        {
            result.Add(new List<string> { proc.ProcessName, GetProcessUser(proc) });
        }
        return resu<
    }

    public static string GetProcessUser(Process process)
    {
        IntPtr tokenHandle = IntPtr.Zero;
        try
        {
            OpenProcessToken(process.Handle, TOKEN_QUERY, out tokenHandle);
            WindowsIdentity wi = new WindowsIdentity(tokenHandle);
            return wi.Name;
        }
        catch
        {
            return null;
        }
        finally
        {
            if (tokenHandle != IntPtr.Zero)
            {
                CloseHandle(tokenHandle);
            }
        }
    }
}
 

Вызов Test.GetProcessWithUsers() (например, в LINQPad) занимает почти 2 секунды для 280 процессов в моей системе.

Я не считаю это приемлемым количеством времени для выполнения этой задачи.

Process.GetProcesses() это быстро, new WindowsIdentity() вклад незначителен, так в чем же задержка OpenProcessToken() ? Существуют ли альтернативные функции Win32 API, которые были бы быстрее?

Комментарии:

1. Процесс. Дескриптор-это проблема (вызывает отказ в доступе). Должно быть намного быстрее, если вы работаете от имени администратора. Вы могли бы написать все это с помощью P/Invoke, чтобы избежать этого, если вам нужна производительность

2. @SimonMourier Я не могу запустить его как администратор, вот в чем дело.

3. Я понимаю, но так ли это быстрее? Если да, то перепишите, используя полный P/Invoke

4. OpenProcessToken все в порядке и не создает никаких проблем или замедления, если у вас есть дескриптор процесса с PROCESS_QUERY_LIMITED_INFORMATION доступом. однако — какой смысл / конечная цель собирать идентификаторы безопасности владельцев ?

5. Вот версия, которая должна быть быстрее (полный p/вызов) pastebin.com/raw/ae5Eg9iP

Ответ №1:

Большая часть используемого времени, по-видимому, вызвана . Process Класс СЕТИ (законно созданное исключение с отказом в доступе и т. Д.), Поэтому вот полная версия P/Invoke, которая его не использует, но использует собственную функцию CreateToolhelp32Snapshot:

 [DllImport("advapi32", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(int dwFlags, int th32ProcessID);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct PROCESSENTRY32
{
    public int dwSize;
    public int cntUsage;
    public int th32ProcessID;
    public IntPtr th32DefaultHeapID;
    public int th32ModuleID;
    public int cntThreads;
    public int th32ParentProcessID;
    public int pcPriClassBase;
    public int dwFlags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szExeFile;
}

public static List<List<string>> GetProcessWithUsers()
{
    var result = new List<List<string>>();
    const int TH32CS_SNAPPROCESS = 2;
    var snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    var entry = new PROCESSENTRY32();
    entry.dwSize = Marshal.SizeOf<PROCESSENTRY32>();
    if (Process32First(snap, ref entry))
    {
        do
        {
            const int PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000;
            var handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, entry.th32ProcessID);
            result.Add(new List<string> { entry.szExeFile, GetProcessUser(handle) });
            if (handle != IntPtr.Zero)
            {
                CloseHandle(handle);
            }
        }
        while (Process32Next(snap, ref entry));
    }
    CloseHandle(snap);
    return resu<
}

public static string GetProcessUser(IntPtr handle)
{
    if (handle == IntPtr.Zero)
        return null;

    const int TOKEN_QUERY = 0x0008;
    if (!OpenProcessToken(handle, TOKEN_QUERY, out var tokenHandle))
        return null;

    var wi = new WindowsIdentity(tokenHandle);
    CloseHandle(tokenHandle);
    return wi.Name;
}
 

На моем компьютере я снизился с 1500 мс до 30 мс (x50).

Комментарии:

1. CreateToolhelp32Snapshot ( особенно Process32Next ) очень плохой дизайн и медленное сравнение NtQuerySystemInformation(SystemProcessInformation..) . если хотите максимальную скорость, конечно, лучше используйте NtQuerySystemInformation

2. Здесь много отсутствующих проверок ошибок и finally блоков

3. @Charlieface — Нет, это не пропущено, меня не интересуют подробности ошибок, я просто сбрасываю все, что могу. Что касается, наконец, здесь возможно не так много бросков (возможно, в конструкторе WindowsIdentity все в порядке), это код взаимодействия. В любом случае, вопрос был не в проверке ошибок.

4. Я думаю, что могу добавить проверку ошибок и предложения finally. «Сбрасываю все, что могу» достаточно хорошо для моего варианта использования.

5. я просто отмечаю, что NtQuerySystemInformation это будет быстрее (я не думаю, что ваше решение плохое, наоборот. просто это будет еще быстрее). почему Process32Next недостаточно хорошо ? потому что он внутренне копировал данные (из NtQuerySystemInformation ) в раздел и при каждом вызове Process32First/Process32Next сопоставлял и разматывал этот раздел и копировал данные в пользовательский буфер. на этой карте/снимается основная точка замедления. также Process32Next по какой-то неизвестной причине отбросьте некоторые нарушения, которые возвращаются по исходному SystemProcessInformation (идентификатор терминальной сессии)