如何向进程发送键而不是字符?

发布于 2024-08-21 07:48:29 字数 135 浏览 6 评论 0原文

System.Diagnostics.Process 公开了一个名为 StandardInput 的 StreamWriter,据我所知,它只接受字符。

但我还需要发送击键,并且某些击键不能很好地映射到字符。

我应该怎么办?

System.Diagnostics.Process exposes a StreamWriter named StandardInput, which accepts only characters as far as I know.

But I need to send keystrokes as well, and some keystrokes don't map well to characters.

What should I do?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

风和你 2024-08-28 07:48:29

您正在将输入流与控制信号混合。正如您所知,控制台进程有一个默认输入流,您可以使用 StandardInput 控制该输入流。但 Ctrl-C 和 Ctrl-Break 不是通过此流发送到进程的字符,而是进程使用注册的信号处理程序接收的控制信号,请参阅CTRL+C 和 CTRL+BREAK 信号

默认情况下,当控制台窗口有
键盘焦点,CTRL+C 或
CTRL+BREAK 被视为信号
(SIGINT 或 SIGBREAK)而不是
键盘输入。

要向进程发送虚假信号,您可以使用 GenerateConsoleCtrlEvent 并发送 CTRL_C_EVENTCTRL_BREAK_EVENT。此 API 没有 .Net 等效项,因此您必须 PInvoke 它。

要从 .NET 使用它,您只需包含函数定义:

const int CTRL_C_EVENT = 0;
const int CTRL_BREAK_EVENT = 1;

[DllImport("kernel32.dll")]
static extern bool GenerateConsoleCtrlEvent(
    uint dwCtrlEvent,
    uint dwProcessGroupId);

You are mixing input streams with control signals. A console process has a default input stream which you can control with the StandardInput, as you already know. But Ctrl-C and Ctrl-Break are not characters sent to the process through this stream, but instead they are instead control signals that the process receives using the registered signal handlers, see CTRL+C and CTRL+BREAK Signals:

By default, when a console window has
the keyboard focus, CTRL+C or
CTRL+BREAK is treated as a signal
(SIGINT or SIGBREAK) and not as
keyboard input.

To send fake signals to a process you can use GenerateConsoleCtrlEvent and send either CTRL_C_EVENT or CTRL_BREAK_EVENT. This API has no .Net equivalent, so you have to PInvoke it.

To use it from .NET you simply need to include the function definition:

const int CTRL_C_EVENT = 0;
const int CTRL_BREAK_EVENT = 1;

[DllImport("kernel32.dll")]
static extern bool GenerateConsoleCtrlEvent(
    uint dwCtrlEvent,
    uint dwProcessGroupId);
如果没有你 2024-08-28 07:48:29

Codeplex 上找到了一个输入模拟器,它可能适合您。
我正在编写示例代码,很快就会发回此处,请记住输入模拟器与 Remus 提供的链接中的内容类似...

编辑: 我发现有有了这个限制,您绝对可以摆脱典型的 System.Windows.Forms.SendKeys.Send 方法,它确实有效! ,但是,该进程必须有

  • 没有流的重定向
  • 不能隐藏窗口(这就是失败的地方,因为窗口的句柄无处可见,无法将其带到前台使其处于活动状态!)
  • 显示此操作生效过程的窗口!

在您的情况下,只需找到窗口,通过 pinvoke 'SetForegroundWindow' 将其设置为活动状态,然后发送序列 ^{BREAK} ,该序列将 Ctrl+Break 信号发送到进程这确实工作得很好(特别是如果该进程是命令行程序/批处理文件)。这是关于 CodeProject 的文章,它完全做到了这一点并反映了 SendKeys...我还没有粘贴一些代码来演示......

编辑#2:实际上我很惊讶......因为这段代码将显示(概念证明)......它正在使用:

  • InputSimulator(如前所述)
  • 一个由按钮组成的Windows窗体,加载窗体时会自动运行该类。单击按钮后,它会向隐藏进程发送 ctrl-break
  • 输出流确实被重定向,并且是一个隐藏窗口。
  • 奇怪的是,输出被捕获,但没有在调试窗口中实时显示结果,也就是说,它被缓冲(我猜)直到进程终止,显示整个输出......
  • 我作弊了关于 FindWindow API 调用的一点,因为我知道窗口的标题过去和现在都能够以某种方式将其带到前台,并使用 InputSimulator 将击键发送给它......或者使用传统的普通旧 SendKeys 函数...我使用 Thread.Sleep 的原因是为了确保发送击键以便“推入键盘队列” “活动前台窗口”,尽管如此,它是隐藏的”
  • 我使用“netstat -e 5”命令永远循环,每 5 秒刷新一次结果,直到收到“Ctrl+C”来打破无限循环。
public partial class Form1 : Form
{
    private TestNetStat netStat = new TestNetStat();
    public Form1()
    {
       InitializeComponent();
       using (BackgroundWorker bgWorker = new BackgroundWorker())
       {
           bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
           bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
           bgWorker.RunWorkerAsync();
       }
    }

    void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
       System.Diagnostics.Debug.WriteLine("BGWORKER ENDED!");
    }

    private void  bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       netStat.Run();
    } 
    void btnPost_Click(object sender, EventArgs e)
    {
       netStat.PostCtrlC();
       System.Diagnostics.Debug.WriteLine(string.Format("[{0}] - {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), this.netStat.OutputData.Replace(Environment.NewLine, "")));
    }
}

public class TestNetStat
{
    private StringBuilder sbRedirectedOutput = new StringBuilder();
    //
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [DllImport("user32")]
    public static extern int SetForegroundWindow(IntPtr hwnd);
    public string OutputData
    {
       get { return this.sbRedirectedOutput.ToString(); }
    }
    public void PostCtrlC()
    {
       IntPtr ptr = FindWindow(null, @"C:\Windows\System32\netstat.exe");
       if (ptr != null)
       {
          SetForegroundWindow(ptr);
          Thread.Sleep(1000);
          WindowsInput.InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.CANCEL);
          // SendKeys.Send("^{BREAK}");
          Thread.Sleep(1000);
        }
    }
    public void Run()
    {
        System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
        ps.FileName = "netstat";
        ps.ErrorDialog = false;
        ps.Arguments = "-e 5";
        ps.CreateNoWindow = true;
        ps.UseShellExecute = false;
        ps.RedirectStandardOutput = true;
        ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
        using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
        {
           proc.StartInfo = ps;
           proc.EnableRaisingEvents = true;
           proc.Exited += new EventHandler(proc_Exited);
           proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
           proc.Start();
           proc.BeginOutputReadLine();
           proc.WaitForExit();
        }
     }

     void proc_Exited(object sender, EventArgs e)
     {
        System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
     }

     void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
     {
         if (e.Data != null)
         {
            this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
            System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
         }
     }
}

撇开挑剔不谈,我知道 netStat 正在“BackgroundWorker”线程上运行,并且我直接从主 GUI 线程调用了“PostCtrlC”方法……作为证明,这是迂腐的——概念代码,但它确实表明它需要实现“ISynchronizeInvoke”以使其线程安全,除此之外......它确实有效。

There's an input Simulator found here on Codeplex which may do just the job for you.
I am working on a sample code and will post back here shortly, bear in mind the Input Simulator is similar to what was found in the link supplied by Remus...

Edit: I have found that there is a limitation with this, you can definitely get away with the typical System.Windows.Forms.SendKeys.Send method, it does work effectively! , but, the process must have

  • No redirections of the streams
  • Cannot be hidden window (this is where it will fail, since the window's handle is nowhere to be seen, no way of bringing it to the foreground to make it active!)
  • A window showing the process for this to be effective!

In your case, it's a matter of finding the window, set it active via pinvoke 'SetForegroundWindow', and send the sequences ^{BREAK} which sends the Ctrl+Break signal to the process which does work very well (especially if the process is a command line program/batch file). Here's an article on CodeProject that does this exactly and mirrors the SendKeys...I have yet to paste some code into this to demonstrate ....

Edit#2: Actually I am quite surprised...as this code will show (proof of concept)...it is using:

  • InputSimulator (as mentioned previously)
  • A windows form that consists of a button, when the form is loaded it automatically runs the class. Upon clicking the button, it posts a ctrl-break to the hidden process
  • The output stream is indeed redirected and is a hidden window.
  • The weird thing, is the output is being captured but does not show the results in the debug window, in real-time that is, it is buffered (I guess) until the process terminates, the whole output is shown...
  • I cheated a bit on the FindWindow API call, because I knew the window's title was and was somehow, able to bring it to the foreground, and using the InputSimulator to send the keystrokes to it...or use the traditional plain old SendKeys function...the reason I had the Thread.Sleep is to ensure that the keystrokes are sent in order to be 'pushed into the keyboard queue of the "active foreground window", which despite that, is hidden'
  • I used the 'netstat -e 5' command to loop forever, refreshing the results every 5 seconds until it receives a 'Ctrl+C' to break the infinite loop.
public partial class Form1 : Form
{
    private TestNetStat netStat = new TestNetStat();
    public Form1()
    {
       InitializeComponent();
       using (BackgroundWorker bgWorker = new BackgroundWorker())
       {
           bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
           bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
           bgWorker.RunWorkerAsync();
       }
    }

    void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
       System.Diagnostics.Debug.WriteLine("BGWORKER ENDED!");
    }

    private void  bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       netStat.Run();
    } 
    void btnPost_Click(object sender, EventArgs e)
    {
       netStat.PostCtrlC();
       System.Diagnostics.Debug.WriteLine(string.Format("[{0}] - {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), this.netStat.OutputData.Replace(Environment.NewLine, "")));
    }
}

public class TestNetStat
{
    private StringBuilder sbRedirectedOutput = new StringBuilder();
    //
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [DllImport("user32")]
    public static extern int SetForegroundWindow(IntPtr hwnd);
    public string OutputData
    {
       get { return this.sbRedirectedOutput.ToString(); }
    }
    public void PostCtrlC()
    {
       IntPtr ptr = FindWindow(null, @"C:\Windows\System32\netstat.exe");
       if (ptr != null)
       {
          SetForegroundWindow(ptr);
          Thread.Sleep(1000);
          WindowsInput.InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.CANCEL);
          // SendKeys.Send("^{BREAK}");
          Thread.Sleep(1000);
        }
    }
    public void Run()
    {
        System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
        ps.FileName = "netstat";
        ps.ErrorDialog = false;
        ps.Arguments = "-e 5";
        ps.CreateNoWindow = true;
        ps.UseShellExecute = false;
        ps.RedirectStandardOutput = true;
        ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
        using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
        {
           proc.StartInfo = ps;
           proc.EnableRaisingEvents = true;
           proc.Exited += new EventHandler(proc_Exited);
           proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
           proc.Start();
           proc.BeginOutputReadLine();
           proc.WaitForExit();
        }
     }

     void proc_Exited(object sender, EventArgs e)
     {
        System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
     }

     void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
     {
         if (e.Data != null)
         {
            this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
            System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
         }
     }
}

Nitpicky aside, I know that the netStat is running off the 'BackgroundWorker' thread, and I directly invoked the 'PostCtrlC' method from the main GUI thread...this is pedantic as a proof-of-concept code, but it does show that it needs to implement 'ISynchronizeInvoke' to make it thread-safe, that aside...it does indeed work.

趁年轻赶紧闹 2024-08-28 07:48:29

您见过这个很棒的工具吗 - AutoIt。这是一个脚本工具。要发送退格键,您可以使用 Send("{BACKSPACE}")

这是一个很棒的工具,它可以帮助自动执行许多手动点击/双击等。

这与您的问题相关吗?

Have you seen this great tool - AutoIt. This is a scripting tool. To send a backspace you would use Send("{BACKSPACE}")

This is a great tool and it can help in automating many manual clicks/double-clicks/etc.

Is this relevant to your question ?

混吃等死 2024-08-28 07:48:29

如果您有一个可以将密钥发送到的 Windows 窗体窗口,则 SendKeys 可能是一个合适的解决方案。

对于按退格键和 Ctrl+C,应该是

SendKeys.Send("{BACKSPACE}^C");

If you have a Windows Forms window that you can send the keys to, then SendKeys might be an appropriate solution.

For pressing backspace and Ctrl+C, that should be

SendKeys.Send("{BACKSPACE}^C");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文