Win32: как получить процесс / поток, владеющий мьютексом?

Я работаю над приложением, у которого в любой момент времени должен существовать только один экземпляр. Для этого есть несколько возможностей:

  • Проверьте запущенные процессы на предмет соответствия имени нашего EXE (ненадежно)
  • Найдите главное окно (ненадежно, и у меня не всегда есть главное окно)
  • Создайте мьютекс с уникальным именем (GUID)

Вариант мьютекса мне кажется наиболее надежным и элегантным.

Однако до того, как мой второй экземпляр завершится, я хочу отправить сообщение уже запущенному экземпляру. Для этого мне нужен дескриптор потока (или процесса), которому принадлежит мьютекс.

Однако, похоже, нет функции API для определения создателя / владельца данного мьютекса. Я просто не замечаю этого? Есть ли другой способ попасть в эту ветку / процесс? Есть ли другой способ сделать это?

Обновление: Этот парень просто отправил сообщение все запущенные процессы. Думаю, это возможно, но мне это не очень нравится ...


person Thomas    schedule 22.12.2009    source источник
comment
Дубликат? stackoverflow.com/questions/19147/   -  person Dead account    schedule 22.12.2009
comment
Не совсем. Ни один из ответов не говорит мне, как получить дескриптор процесса.   -  person Thomas    schedule 22.12.2009
comment
Вот дубликат, хотя ответа на него так и не последовало: stackoverflow.com/questions/541477/   -  person Eric Petroelje    schedule 22.12.2009
comment
Что ж, на него ответили, но, вероятно, не так, как надеялся вопрошающий :)   -  person Eric Petroelje    schedule 22.12.2009
comment
Я бы тоже не назвал это точным дубликатом; хотя он отвечает на мой основной вопрос, он не предлагает альтернативного решения - то, о чем я явно просил.   -  person Thomas    schedule 22.12.2009
comment
Не ставлю это как ответ (поскольку это не ответ), но это может быть интересно: msdn.microsoft.com/en-us/library/aa446629%28VS.85%29.aspx показывает, как получить владельца (в смысле пользователя) файловая блокировка, и вы можете применить то же самое к мьютексу. Тем не менее, все еще не дает вам PID.   -  person Logan Capaldo    schedule 22.12.2009


Ответы (5)


Я не думаю, что существует тривиальный способ определить фактического владельца мьютекса, но процесс, которому он принадлежит, может создавать другие вторичные элементы, время жизни которых привязано к нему. Существует множество механизмов, которые подходят для обратного вызова через процесс без главного окна.

  1. Зарегистрируйте объект в таблице запущенных объектов COM. Клиенты, которые не могут стать владельцем Mutex, могут найти владельца через ROT и перезвонить владельцу. Файловое прозвище должно быть подходящим для регистрации здесь.
  2. Создайте блок общей памяти, содержащий сведения о местоположении для процесса-владельца. Оттуда запишите в буфер дескриптор процесса и дескриптор потока потока, который может получать сообщения Windows, а затем используйте PostThreadMessage () для отправки уведомления. Любой другой конкурирующий процесс может открыть общую память только для чтения, чтобы определить, куда отправить сообщение Windows.
  3. Слушайте в процессе владельца сокета или именованного канала. Вероятно, излишек и не подходит для ваших нужд.
  4. Используйте общий файл с блокировкой. Мне это не нравится, потому что владельцу потребуется опрос, и он не сможет корректно обработать N потенциальных других процессов, которые могут одновременно пытаться связаться с владельцем.

Вот справочные ссылки для первых двух вариантов.

  1. IRunningObjectTable @ MSDN, Моникеры файлов @ MSDN
  2. Создание именованной общей памяти @ MSDN
person meklarian    schedule 22.12.2009

Это должно помочь вам начать работу с исходным запросом на получение процесса, владеющего мьютексом.

Это на C #, но вызовы Win32 такие же.

class HandleInfo
{
    [DllImport("ntdll.dll", CharSet = CharSet.Auto)]
    public static extern uint NtQuerySystemInformation(int SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr VirtualAlloc(IntPtr address, uint numBytes, uint commitOrReserve, uint pageProtectionMode);

    [DllImport("kernel32.dll", SetLastError=true)]
    internal static extern bool VirtualFree(IntPtr address, uint numBytes, uint pageFreeMode);

    [StructLayout(LayoutKind.Sequential)]
    public struct SYSTEM_HANDLE_INFORMATION
    {
        public int ProcessId;
        public byte ObjectTypeNumber;
        public byte Flags; // 1 = PROTECT_FROM_CLOSE, 2 = INHERIT
        public short Handle;
        public int Object;
        public int GrantedAccess;
    }

    static uint MEM_COMMIT = 0x1000;
    static uint PAGE_READWRITE = 0x04;
    static uint MEM_DECOMMIT = 0x4000;
    static int SystemHandleInformation = 16;
    static uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;

    public HandleInfo()
    {
        IntPtr memptr = VirtualAlloc(IntPtr.Zero, 100, MEM_COMMIT, PAGE_READWRITE);

        int returnLength = 0;
        bool success = false;

        uint result = NtQuerySystemInformation(SystemHandleInformation, memptr, 100, out returnLength);
        if (result == STATUS_INFO_LENGTH_MISMATCH)
        {
            success = VirtualFree(memptr, 0, MEM_DECOMMIT);
            memptr = VirtualAlloc(IntPtr.Zero, (uint)(returnLength + 256), MEM_COMMIT, PAGE_READWRITE);
            result = NtQuerySystemInformation(SystemHandleInformation, memptr, returnLength, out returnLength);
        }

        int handleCount = Marshal.ReadInt32(memptr);
        SYSTEM_HANDLE_INFORMATION[]  returnHandles = new SYSTEM_HANDLE_INFORMATION[handleCount];

        using (StreamWriter sw = new StreamWriter(@"C:\NtQueryDbg.txt"))
        {
            sw.WriteLine("@ Offset\tProcess Id\tHandle Id\tHandleType");
            for (int i = 0; i < handleCount; i++)
            {
                SYSTEM_HANDLE_INFORMATION thisHandle = (SYSTEM_HANDLE_INFORMATION)Marshal.PtrToStructure(
                    new IntPtr(memptr.ToInt32() + 4 + i * Marshal.SizeOf(typeof(SYSTEM_HANDLE_INFORMATION))),
                    typeof(SYSTEM_HANDLE_INFORMATION));
                sw.WriteLine("{0}\t{1}\t{2}\t{3}", i.ToString(), thisHandle.ProcessId.ToString(), thisHandle.Handle.ToString(), thisHandle.ObjectTypeNumber.ToString());
            }
        }

        success = VirtualFree(memptr, 0, MEM_DECOMMIT);
    }
}
person GalacticJello    schedule 22.12.2009
comment
NtQuerySystemInformation может быть изменен или недоступен в будущих версиях Windows. Приложения должны использовать альтернативные функции, перечисленные в этом разделе. А SystemHandleInformation полностью недокументирован. Ой! - person Thomas; 23.12.2009
comment
На самом деле, я просто пытался тебе помочь. Мне пришлось откопать кое-что из моего эзотерического кода на C ++ еще давно, но вместо того, чтобы возвращаться в VC6, я решил перенести его на C # для вас и других (чтобы сделать его немного более актуальным). Думаю, я не знал, что нельзя использовать недокументированные вещи, вы просто спросили, возможно ли это ... - person GalacticJello; 12.01.2010
comment
Затем из этого кода вы должны пойти и позвонить NtQueryMutant, чтобы получить статус владельца и PID / ThreadID владельца. - person josh poley; 24.08.2013

Я никогда по-настоящему не понимал рациональности использования Mutex, который не имеет возможности передачи сигналов. Вместо этого я бы создал событие (используя CreateEvent), которое имеет те же свойства, что и создание мьютекса (то есть с именем, которое он может вернуть, что объект уже существует), но вы можете установить флаг события в новом процессе, если исходный процесс ожидает флага события, он может быть уведомлен, когда ему нужно проснуться.

person tyranid    schedule 22.12.2009
comment
Конечно, исходный процесс не блокирует ожидание события; он просто бегает по кругу в своем цикле сообщений. (Я мог бы проверять мьютекс каждый раз в цикле, но это плохо.) - person Thomas; 22.12.2009
comment
Вы всегда можете запустить другой поток, заставить его спать по событию и публиковать сообщения в цикле сообщений, когда что-то происходит. - person Thanatos; 22.12.2009
comment
Существует также MsgWaitForMultipleObjects, который позволяет вам ждать прибытия сообщения Windows или ожидаемого дескриптора (например, события или мьютекса), чтобы вы могли сделать это в одном потоке. - person tyranid; 23.12.2009

Вы всегда можете сделать это способом UNIX и создать файл «pid», поместив в этот файл идентификатор процесса текущего запущенного экземпляра. Затем попросите приложение удалить файл при выходе.

Когда запускается новый экземпляр, он должен проверить, что процесс в PID-файле также действительно активен (в случае, если приложение завершает работу ненормально и файл не удаляется).

person Eric Petroelje    schedule 22.12.2009
comment
+1. Вы даже можете использовать файловую блокировку для этого файла вместо отдельного Mutex. - person Logan Capaldo; 22.12.2009
comment
Подобно этому, можете ли вы использовать общую память и записать туда PID? - person Thanatos; 22.12.2009
comment
На самом деле нет необходимости проходить через файловую систему ... часть разделяемой памяти (CreateFileMapping) подойдет. Спасибо, что поставили меня на этот трек. Я приму это, если кто-нибудь не предложит лучшее решение. - person Thomas; 22.12.2009

Создайте общую область памяти с фиксированным именем:

http://msdn.microsoft.com/en-us/library/aa366551%28VS.85%29.aspx

Затем вы можете поместить внутрь любую структуру, которая вам нравится, включая идентификатор процесса, HWND и т. Д.

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

Надеюсь, это поможет...

person Daniel Nikolić    schedule 18.11.2010
comment
В общем, розетки для этого плохи. Для них требуется жестко запрограммировать номер порта; что, если этот номер уже был бы занят? Создание прослушивающего соединения также делает приложение очень подозрительным, если его функции не требуют этого. Я бы счел, скажем, блокнот, который принимает входящие соединения, 100% вредоносной программой. Пока рассматривается программное обеспечение конечного пользователя, связь через сокеты разрешается только для определенных приложений, таких как сервер и интерфейс FileZilla. Конечно, для корпоративного программного обеспечения может потребоваться любая опция. Я бы порекомендовал для этого именованные каналы. - person Fr0sT; 24.03.2014