Find out programmatically if a process is demandin

2019-02-10 10:06发布

问题:

How can I programmatically (in C#) determine, if ANOTHER foreign application (native, java, .NET or whatever...) is currently demanding user input? Can this be done fully in Managed code?

What I'm looking for is the implementation of:

static Boolean IsWaitingForUserInput(String processName)
{
    ???
}

By demanding user input I mean when an application asks the user to enter some data or quit an error message (Modal dialogs) and is not able to perform its normal tasks anymore. A drawing application that is waiting for the user to draw something is not meant here.

PS: After edits to reflect the comments at the bottom and make the concern clearer, some comments and answers may not be 100% consistent with the question. Take this into account when evaluating the answers and remarks.

回答1:

It's in general impossible. Take for instance a common kind of application, a word processor. Nowadays that will be running spellchecks in the background, it periodically auto-saves your document, etcetera. Yet from a users perspective it's waiting for input all the time.

Another common case would be a slideshow viewer. At any moment in time you could press a key to advance a slide. Yet your typical user would not view this as "waiting for input".

To summarize: "waiting for input" is a subjective state and therefore cannot be determined programmatically.



回答2:

How do you like this?

I worked out a solution that seems to work, please notify me in case of problems with this code so I also gain benefit of improvements. It works for Excel as far as I tested. The only issue I dislike is that I had to use unmanaged calls. It also handles the case when an application is based on a dialog like for MFC, derived from CDialog.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace Util
{
    public class ModalChecker
    {
        public static Boolean IsWaitingForUserInput(String processName)
        {
            Process[] processes = Process.GetProcessesByName(processName);
            if (processes.Length == 0)
                throw new Exception("No process found matching the search criteria");
            if (processes.Length > 1)
                throw new Exception("More than one process found matching the search criteria");
            // for thread safety
            ModalChecker checker = new ModalChecker(processes[0]);
            return checker.WaitingForUserInput;
        }

        #region Native Windows Stuff
        private const int WS_EX_DLGMODALFRAME = 0x00000001;
        private const int GWL_EXSTYLE = (-20);
        private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
        [DllImport("user32")]
        private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
        [DllImport("user32")]
        private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
        #endregion

        // The process we want the info from
        private Process _process;
        private Boolean _waiting;

        private ModalChecker(Process process)
        {
            _process = process;
            _waiting = false; //default
        }

        private Boolean WaitingForUserInput
        {
            get
            {
                EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
                return _waiting;
            }
        }

        private int WindowEnum(IntPtr hWnd, int lParam)
        {
            if (hWnd == _process.MainWindowHandle)
                return 1;
            IntPtr processId;
            GetWindowThreadProcessId(hWnd, out processId);
            if (processId.ToInt32() != _process.Id)
                return 1;
            uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
            if ((style & WS_EX_DLGMODALFRAME) != 0)
            {
                _waiting = true;
                return 0; // stop searching further
            }
            return 1;
        }
    }
}


回答3:

If I understand you well, you may try to enumerate the process's threads and check their states. Windows Task Manager does something similar. This however will require Win32 functions - Thread32First and Thread32Next among others - but you can achieve this by the simplest use of P/Invoke in C#:

    [DllImport("Executor.dll")]
    public static extern bool Thread32First(IntPtr handle, IntPtr threadEntry32);

(Precise signature may differ).

EDIT: Ok, there are corresponding functions in the .NET library.



回答4:

If possible, rewrite the other code to be a concurrent input processor (similar to the algorithm for a concurrent web server):

Wait for input
Fork process
  Parent: Repeat
  Child: (Worker) handle input

Of course, you could still have your function:

static Boolean IsWaitingForUserInput(String processName) {
    return true;
}