如何判断一个进程是否具有图形界面?

11
我正在使用自动化测试应用程序,但有时我想通过批处理文件启动该应用。 当我运行“process.WaitForInputIdle(100)”时,会出现以下错误信息:
“WaitForInputIdle失败。这可能是因为进程没有图形界面。”
如何确定进程是否具有图形界面?
5个回答

10

请查看Environment.UserInteractive,该方法可以确定进程是否拥有界面,例如服务不具有用户交互性。

您还可以查看Process.MainWindowHandle,该方法将告诉您是否存在图形界面。

这两个检查的结合应该涵盖所有可能性。


不幸的是,批处理文件似乎是用户交互式的(这对于批处理文件和窗口都返回“true”)。 - Lunivore
当你说Process.MainWindowHandle可以用来做这个 - 怎么做?能否在另一个答案中解释一下? - Lunivore
2
如果 Process.MainWindowHandle == IntPtr.Zero,它就没有主窗口。 - Bear Monkey
1
Environment.UserInteractive 是用于当前进程的属性。 - Bear Monkey
谢谢@Bear Monkey,当然是这样。还有关于MainWindowHandle的想法,我会试一试。 - Lunivore
好的,这是我目前为止最好的答案。虽然不能解决所有问题,但解决了这一个。谢谢! - Lunivore

1

你可以简单地尝试并捕获异常:

Process process = ...
try
{
    process.WaitForInputIdle(100);
}
catch (InvalidOperationException ex)
{
    // no graphical interface
}

是的,我可以...但出于某种原因,这似乎很丑陋。大多数好的库和API通常都有一种确定即将执行的操作是否有效的方法。微软通常会提供此功能。如果您不介意,我会等一天,看看是否还有其他人有任何想法。 - Lunivore
我同意,像这样使用异常确实很丑陋。 - Bear Monkey
@Lunivore,@Bear Monkey:在我看来,这似乎是介于“令人烦恼的异常”和“外生异常”之间的一个异常情况。为了避免抛出异常,您可以使用P/Invoke WaitForInputIdle函数。 - Dirk Vollmar
@Bear Monkey:说得好,但在这种特定情况下,您可能不会获得任何有用的信息。在幕后调用的WaitForInputIdle函数在任何四种错误情况下都会返回WAIT_FAILED - Dirk Vollmar
是的,你说得对。我以为它使用 HRESULTS,但实际上并不是。当我意识到我的错误时,在你回复之前我已经删除了我的帖子。 - Bear Monkey

1

我在考虑这条路线,虽然还是有些丑陋,但试图避免异常。

Process process = ...

bool hasUI = false;

if (!process.HasExited)
{
    try
    {
        hasUI = process.MainWindowHandle != IntPtr.Zero;
    }
    catch (InvalidOperationException)
    {
        if (!process.HasExited)
            throw;
    }
}

if (!process.HasExited && hasUI)
{

    try
    {
        process.WaitForInputIdle(100);
    }
    catch (InvalidOperationException)
    {
        if (!process.HasExited)
            throw;
    }
}

谢谢,这对我很有效,太棒了。我注意到在某些情况下,服务也可以附加控制台,那么之前提到的解决方案就不起作用了。但是另一方面,在构建属性中可以将EXe构建为Windows应用程序,然后WaitForInputIdle就不起作用了。 MainWindowHandle和WaitForInputHandle的组合效果很好! - b.kiener

1
除了 Process.MainWindowHandle 之外,您可以通过读取进程主模块的 PE 头来检测进程模块子系统,从而提高解决方案的效率。如果子系统是 IMAGE_SUBSYSTEM_WINDOWS_GUI,则该进程可以具有图形界面。

使用此类来读取进程主模块的 PE 头: https://gist.github.com/ahmedosama007/bfdb8198fe6690d17e7c3db398f6d725

使用以下代码来检测进程模块子系统:

Dim peReader = New PEHeaderReader("C:\Windows\notepad.exe")

Dim subsystem As PEHeaderReader.ImageSubSystem

If peReader.Is32BitHeader Then '32-bit
    subsystem = peReader.OptionalHeader32.Subsystem
Else '64-bit
    subsystem = peReader.OptionalHeader64.Subsystem
End If

'https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32

If subsystem = PEHeaderReader.ImageSubSystem.IMAGE_SUBSYSTEM_WINDOWS_GUI Then
    Console.WriteLine("GUI")
ElseIf subsystem = PEHeaderReader.ImageSubSystem.IMAGE_SUBSYSTEM_WINDOWS_CUI Then
    Console.WriteLine("Console")
Else
    Console.WriteLine("Other Subsystem")
End If

Console.ReadLine()

1
谢谢Ahmed,但愿你在12年前我提出这个问题的时候就在身边了! - Lunivore

1
除了检查MainWindowHandle之外,可以枚举进程线程,并通过P/Invokes检查它们是否引用可见窗口。这似乎很好地捕捉到第一个检查遗漏的任何窗口。
private Boolean isProcessWindowed(Process externalProcess)
{
    if (externalProcess.MainWindowHandle != IntPtr.Zero)
    {
        return true;
    }

    foreach (ProcessThread threadInfo in externalProcess.Threads)
    {
        IntPtr[] windows = GetWindowHandlesForThread(threadInfo.Id);

        if (windows != null)
        {
            foreach (IntPtr handle in windows)
            {
                if (IsWindowVisible(handle))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

private IntPtr[] GetWindowHandlesForThread(int threadHandle)
{
    results.Clear();
    EnumWindows(WindowEnum, threadHandle);

    return results.ToArray();
}

private delegate int EnumWindowsProc(IntPtr hwnd, int lParam);

private List<IntPtr> results = new List<IntPtr>();

private int WindowEnum(IntPtr hWnd, int lParam)
{
    int processID = 0;
    int threadID = GetWindowThreadProcessId(hWnd, out processID);
    if (threadID == lParam)
    {
        results.Add(hWnd);
    }

    return 1;
}

[DllImport("user32.Dll")]
private static extern int EnumWindows(EnumWindowsProc x, int y);
[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
[DllImport("user32.dll")]
static extern bool IsWindowVisible(IntPtr hWnd);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接