Win32:如何获取拥有互斥锁的进程/线程?

时间:2009-12-22 16:02:59

标签: winapi process multithreading mutex single-instance

我正在处理一个应用程序,在任何给定时间只能存在一个实例。有几种可能性来实现这一目标:

  • 检查一个与我们的EXE名称相匹配的运行流程(不可靠)
  • 找到主窗口(不可靠,我并不总是有一个主窗口)
  • 创建具有唯一名称(GUID)的互斥锁

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

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

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

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

6 个答案:

答案 0 :(得分:10)

这应该让您开始使用原始请求来获取拥有互斥锁的进程。

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

答案 1 :(得分:4)

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

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

    1. IRunningObjectTable @ MSDNFile Monikers @ MSDN
    2. Creating Named Shared Memory @ MSDN

答案 2 :(得分:2)

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

答案 3 :(得分:2)

使用固定名称

创建共享内存区域

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

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

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

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

答案 4 :(得分:1)

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

当一个新实例启动时,它应该验证PID文件中的进程是否实际存活(如果应用程序异常退出并且文件没有被删除)

答案 5 :(得分:0)

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

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

然后我想也许可以用互斥锁存储额外的数据。但我没有看到用户数据可以存储在互斥对象或事件对象中的何处。但是互斥锁知道它属于哪个线程,从而知道它属于哪个进程。但是如你所说,这个api好像不存在。

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

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