【问题标题】:Win32: How to get the process/thread that owns a mutex?Win32:如何获取拥有互斥锁的进程/线程?
【发布时间】:2010-12-29 04:08:19
【问题描述】:

我正在开发一个在任何给定时间都必须存在一个实例的应用程序。有几种方法可以做到这一点:

  • 检查正在运行的进程是否有与我们的 EXE 名称匹配的进程(不可靠)
  • 找主窗口(不靠谱,而且我也不总是有主窗口)
  • 创建具有唯一名称 (GUID) 的互斥锁

在我看来,互斥锁选项是最可靠和最优雅的。

但是,在我的第二个实例终止之前,我想向已经运行的实例发布一条消息。为此,我需要拥有互斥锁的线程(或进程)的句柄。

但是,似乎没有 API 函数可以获取给定互斥锁的创建者/所有者。我只是忽略它吗?有没有其他方法可以到达这个线程/进程?有没有其他方法可以解决这个问题?

更新This guy 只是向所有正在运行的进程广播一条消息。我想这是可能的,但我真的不喜欢它......

【问题讨论】:

  • 并非如此。没有一个答案告诉我如何获取进程句柄。
  • 这是一个重复的,虽然它从未被回答:stackoverflow.com/questions/541477/…
  • 嗯,已经回答了,但可能不是提问者希望的那样:)
  • 我也不会称其为完全相同的副本;虽然它回答了我的主要问题,但它没有提供替代解决方案——这是我明确要求的。

标签: winapi process multithreading mutex single-instance


【解决方案1】:

这应该让您开始处理原始请求以获取拥有互斥锁的进程。

它在 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);
    }
}

【讨论】:

  • “NtQuerySystemInformation 可能会在未来的 Windows 版本中发生更改或不可用。应用程序应使用本主题中列出的替代功能。” SystemHandleInformation 完全没有文档记录。哎呀!
  • 其实我只是想帮你。我不得不从很久以前挖掘我的一些深奥的 C++ 代码,但我决定将它移植到 C# 以供您和其他人使用,而不是拖回 VC6(以使其更加最新)。我想我不知道你不能使用无证的东西,你只是问是否可能......
  • 从这段代码中你可以调用NtQueryMutant来获取所有者状态和所有者的PID/ThreadID。
【解决方案2】:

我认为没有一种简单的方法可以解决 Mutex 的实际所有者,但是拥有它的进程可以创建其他生命周期与之相关的辅助项。有很多机制适合在没有主窗口的情况下跨进程回调。

  1. 在 COM 运行对象表中注册一个对象。无法获得 Mutex 所有权的客户端可以通过 ROT 查找所有者并回调所有者。文件名字对象应该适合在此处注册。
  2. 创建包含所有者进程位置详细信息的共享内存块。从那里,将可以接收 Windows 消息的线程的进程句柄和线程句柄写入缓冲区,然后使用 PostThreadMessage() 发送通知。任何其他竞争进程都可能以只读方式打开共享内存以确定将 Windows 消息发送到何处。
  3. 侦听套接字或命名管道上的所有者进程。可能矫枉过正,不能很好地满足您的需求。
  4. 使用带锁定的共享文件。我不喜欢这个,因为所有者需要轮询,并且它不会优雅地处理 N 个可能同时尝试联系所有者的潜在其他进程。

这里是前两个选项的参考链接。

  1. IRunningObjectTable @ MSDN , File Monikers @ MSDN
  2. Creating Named Shared Memory @ MSDN

【讨论】:

    【解决方案3】:

    我从来没有真正理解使用没有信号功能的 Mutex 背后的原因。相反,我会创建一个事件(使用 CreateEvent),它具有与创建互斥锁相同的属性(即,它可以返回对象已经存在的名称)但是您可以在新进程中设置事件标志,只要原始进程正在等待事件标志,它可以在需要唤醒自己时得到通知。

    【讨论】:

    • 当然,原来的进程并不是阻塞等待事件;它只是在其消息循环中运行圆圈。 (我可以每次通过循环检查互斥锁,但这感觉很糟糕。)
    • 你总是可以运行另一个线程,让它在事件上休眠,并在发生事情时发布到消息循环。
    • 还有 MsgWaitForMultipleObjects 允许您等待 Windows 消息到达或等待句柄(比如事件或互斥体)发出信号,从而允许您在一个线程中完成。
    【解决方案4】:

    您始终可以使用 UNIX 方式并创建一个“pid”文件,将当前运行实例的进程 ID 放入该文件中。然后让应用在退出时删除文件。

    当一个新的实例启动时,它应该验证 PID 文件中的进程是否真的还活着(以防应用异常退出并且文件没有被删除)

    【讨论】:

    • +1。您甚至可以在此文件上使用文件锁,而不是单独的互斥锁。
    • 和这个类似,能不能用共享内存,把PID写在那里?
    • 不需要通过文件系统,实际上......一块共享内存(CreateFileMapping)就可以了。谢谢你让我走上这条轨道。除非有人提出更好的解决方案,否则我会接受。
    【解决方案5】:

    使用固定名称创建一个共享内存区

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

    然后你可以在里面放任何你喜欢的结构,包括进程id,HWND等。

    有一个可移植的选项:在端口上创建一个套接字(具有固定编号)并等待(接受)它。该应用程序的第二个实例将失败,因为该端口已被占用。然后第二个实例可以连接到主实例的套接字并发送所需的任何信息。

    我希望这会有所帮助...

    【讨论】:

    • 一般来说,sockets 不适合这个。它们需要一些端口号进行硬编码;如果这个号码已经被占用了怎么办?如果应用程序的功能不需要这样做,创建监听连接也会使应用程序非常可疑。例如,我认为接受传入连接的记事本是 100% 恶意软件。只要考虑到最终用户软件,socket 通信只适用于 FileZilla 服务器和接口等特定应用程序。当然,企业软件可能需要它想要的任何选项。我建议为此使用命名管道。
    【解决方案6】:

    我也有类似的问题。我想要一个在应用程序的单个实例正在运行时返回的函数。然后是另一个将应用程序带到前面的功能。其中我必须首先推导出已经运行的窗口的 HWND。

    FindWindow 很糟糕。窗口标题可以改变,另一个窗口可以使用相同的类和标题等。

    然后我想也许可以用互斥锁存储额外的数据。但我看不到用户数据可以存储在互斥对象或事件对象中的什么位置。但是互斥体知道它属于哪个线程,从而知道它属于哪个进程。但正如你所说,api似乎不存在。

    这里提出了许多新的和复杂的方法;除了简单地使用文件。所以我想添加另一种方法,临时注册表项。

    这个方法对我来说是最简单的,因为我已经构建了一个 hkey 库。但是与看起来可怕的共享内存方法相比,win32 注册表 api 非常简单。

    【讨论】:

      猜你喜欢
      • 2014-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-02
      • 2013-01-31
      • 2012-12-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多