设置隐藏 window 的 window 状态

Set the window state of a hidden window

前段时间我问过这个问题,这里解决了:

但是现在,由于未知原因,那里提供的 C# 或 Vb.Net 代码不起作用,我不明白为什么不起作用。

我对那里提供的原始代码做了一些修改,但我测试了原始代码并没有工作。

发生的事情是我无法取消隐藏的进程,我不确定我失败的地方。乍一看,我认为我用 FindWindowEx 得到的句柄与我想要的句柄并不完全对应。

这些是我的 P/Invoking 函数签名和显示 window 枚举:

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto, 
           BestFitMapping:=False, ThrowOnUnmappablechar:=True)>
Friend Shared Function FindWindow(
                 ByVal lpClassName As String,
                 ByVal lpWindowName As String
) As IntPtr
End Function

<DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, 
           BestFitMapping:=False, ThrowOnUnmappablechar:=True)>
Friend Shared Function FindWindowEx(
                 ByVal hwndParent As IntPtr,
                 ByVal hwndChildAfter As IntPtr,
                 ByVal strClassName As String,
                 ByVal strWindowName As String
) As IntPtr
End Function

<DllImport("user32.dll")>
Friend Shared Function GetWindowThreadProcessId(
                 ByVal hWnd As IntPtr,
                 ByRef processId As Integer
) As Integer
End Function

<DllImport("User32", SetLastError:=False)>
Friend Shared Function ShowWindow(
                 ByVal hwnd As IntPtr,
                 ByVal nCmdShow As ProcessUtil.WindowState
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

Public Enum WindowState As Integer
    Hide = 0
    Normal = 1
    ShowMinimized = 2
    Maximize = 3
    ShowMaximized = Maximize
    ShowNoActivate = 4
    Show = 5
    Minimize = 6
    ShowMinNoActive = 7
    ShowNA = 8
    Restore = 9
    ShowDefault = 10
    ForceMinimize = 11
End Enum

函数:

Public Function SetWindowState(ByVal p As Process,
                               ByVal windowState As ProcessUtil.WindowState) As Boolean

    Dim pHandle As IntPtr = IntPtr.Zero
    Dim pid As Integer

    ' If window is visible then...
    If (p.MainWindowHandle <> IntPtr.Zero) Then
        Return ProcessUtil.NativeMethods.ShowWindow(p.MainWindowHandle, windowState)

    Else ' window is hidden.

        ' Check all open windows (not only the process we are looking), 
        ' begining from the child of the desktop.
        While (pid <> p.Id)

            ' Get child handle of window who's handle is "pHandle".
            pHandle = NativeMethods.FindWindowEx(IntPtr.Zero, pHandle, Nothing, Nothing)

            ' Get PID from "pHandle".
            NativeMethods.GetWindowThreadProcessId(pHandle, pid)

        End While

        Return NativeMethods.ShowWindow(pHandle, windowState)

    End If

End Function

以及我尝试测试该功能的方式,首先我隐藏了记事本进程的 window,然后我尝试取消隐藏它。

Dim p As Process = Process.GetProcessesByName("notepad").First
ProcessUtil.SetWindowState(p, ProcessUtil.WindowState.Hide)

' I find again the process to renew the "p.MainWindowHandle" as IntPtr.Zero.
p = Process.GetProcessesByName("notepad").First
ProcessUtil.SetWindowState(p, ProcessUtil.WindowState.Restore)

记事本的问题在于它有 3 个 windows(spy++class 名称):

1. "Notepad"
2. "MSCTFIME UI"
3. "IME"

你得到的是第二个句柄(反正我得到了),MSCTFIME UI,这就是你不能显示它的原因。您需要指定 class 名称 Notepad 以获得正确的句柄:

pHandle = FindWindowEx(IntPtr.Zero, pHandle, "Notepad", Nothing)

现在是您开始使用 .NET Framework 源代码的时候了,这样您就可以发现为什么这样的代码自己无法工作。访问 Reference Source web site 开始。

在搜索框中键入 "Process.MainWindowHandle",您将到达 this page。很容易看出是 ProcessManager.GetMainWindowHandle 完成了工作。点击 "GetMainWindowHandle"。很小,点击"FindMainWindow"。请注意它是如何枚举 windows 的,向右滚动一点可以看到它 "EnumWindowsCallback" 完成了工作。点击它,很容易看到它"IsMainWindow"决定一个window是否为主。点击它:

    bool IsMainWindow(IntPtr handle) {

        if (NativeMethods.GetWindow(new HandleRef(this, handle), NativeMethods.GW_OWNER) != (IntPtr)0
            || !NativeMethods.IsWindowVisible(new HandleRef(this, handle)))
            return false;

        return true;
    }

换句话说,它不是主window句柄,如果它是自有的window。 或者如果它不可见

后一个条款当然是你的克星。你隐藏了 window,现在框架代码不再认为它可以是主要的 window。所以 Process.MainWindowHandle returns IntPtr.Zero 你的代码不能再工作了。

一个明显的解决方法是简单地重写 .NET Framework 代码并跳过 IsWindowVisible() 测试。然而,这是一个重要的测试,它避免了发现 "message windows",几乎任何进程都会创建这种类型。它们用于内部管道。另一个答案提到了他们。你可以用 Spy++ 看到它们,Notepad 有两个。不是按照让您找到它们的顺序创建的,您将首先找到主要的 window。不能保证在任何过程中都会发生这种情况。

正确的解决方法是你必须不要忘记你做了一些非常对window不友好的事情。记事本本身从不隐藏它的主要window。你基本上把它变成了僵尸,用户不能再做任何事情来恢复 window。剩下的唯一选择是使用任务管理器终止进程。因此,让它再次可见完全是您的工作。您不能忽略的工作,例如,您必须在程序退出之前恢复它们。

或者干脆不要这样做,像这样扰乱用户的 windows 简直就是敌意。