我有一个C#程序,它希望与用C++编写的外部进程进行交互。我相信这个C++过程使用的是正确的标准输入。我只是无法让我的C#代码在试图写到Process.StandardInput时不挂起。
在编写文章时,我已经看到了无数使用Process.StandardInput.Close()
的例子。我找到的每一个StackOverflow答案都说要使用这个,它确实有效。问题是我不能关闭StreamWriter,因为我还没有完成与进程的交互。该进程是一个状态机,它保存使用stdin创建的变量,解析表达式,并返回一个计算。在每次输出之后,我都要继续给出流程输入。
有没有人有这样一个示例,在不关闭或重新启动进程的情况下使用Process.StandardInput.WriteLine
不止一次?
这就是C++进程读取输入的方式。此示例简单地回显输入并等待另一个输入。
int main () {
std::string input;
while (getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
我的C#程序试图使用这个包装类与这个过程交互。
public class Expression {
System.Diagnostics.Process p;
public Expression () {
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "InputEcho.exe";
p.Start();
p.StandardInput.AutoFlush = true;
}
public void Run (in string input, out string output) {
p.StandardInput.WriteLine(input);
// p.StandardInput.Close();
output = p.StandardOutput.ReadToEnd();
}
}
当我取消注释p.StandardInput.Close()
时,这是可行的,但随后对Expression.Run()
的调用将无法工作,因为编写器已经关闭。
主程序
Expression expn = new();
string output;
Console.WriteLine("Expression start");
expn.Run("Hello", output);
Console.WriteLine(output);
expn.Run("Hi", output);
Console.WriteLine(output);
预期产出
Expression start
Hello
Hi
实际输出
Expression start
编辑:
@Matthew给出了一个很好的答案,但这并不是我想要的结果。我没有想过使用事件委托来接收输出数据,我明白为什么:很难将其实现到我想要用来构建与流程相关的API的包装器中。我的意思是,我想编写一些方法来与进程通信,给它输入,接收输出,并在执行其他操作之前将这些数据返回给调用者。我的Expression.Run
方法完美地证明了这一点。
下面是根调用者在更大的C#程序中的样子的一个例子。
bool GetConditionEval (string condition, SomeDataType data) {
// Makes another call to 'Run' that commands the C++ process to store a variable
// Input looks like this: "variableName = true" (aka key/value pairs)
Expression.SetVar(data.name, "true");
// Don't ask why I'm using an external process to set variables using string expressions.
// It's a company proprietary thing.
string output;
Expression.Run(in condition, out output);
if (output.ToLower() == "true") return true;
else if (output.ToLower() == "false") return false;
else throw new Exception("Output is something other than true or false.");
}
这就是为什么我希望Run
立即返回它从进程接收到的输出。如果没有,我想我可以为委托方法找到一种方法,将输出存储在全局容器中,而GetConditionEval
可以直接进入这个范围。不过,我担心比赛的情况。
附带注意:由于我确实希望包含在这个C++过程中的API最终会以其他形式出现,因此将其作为一个独立的进程来处理,并且通过stdin调用API实际上是暂时的,因此我不必将数千行C++代码转换为C#。
解决办法:
我想出了一个解决方案,使用Matthew建议的异步方法,同时具有一个线性过程,即发送输入,并以相同的顺序立即关闭输出。我重新配置了包装类,以对从事件侦听器接收的每个输出进行排队。这就建立了一个模式,在这个模式中,我可以调用一个方法来发送输入,然后立即调用另一个方法将输出数据弹出队列(如果有的话)。我弥补了这样一个事实,即如果队列是空的,那么如果队列是空的,那么输出数据就不可能立即被保存下来,然后在有什么东西存在时继续前进。如果它需要等待的话,这会使它成为一个阻塞的呼叫,但这是迄今为止我所拥有的最好的。我还实现了一个故障安全,这样它就不会不确定地等待。
public class Expression {
System.Diagnostics.Process p = new();
System.Collections.Generic.Queue<string> outputQ = new();
public Expression () {
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "C2E2.exe";
p.OutputDataReceived += (s, e) => {
outputQ.Enqueue(e.Data);
};
p.Start();
p.BeginOutputReadLine();
}
/// Returns custom exception object if error is encountered.
public GRLib.Exception Run (in string input) {
if (p == null) return GRLib.Exception.New("Expression Evaluator not operational.");
try {
p.StandardInput.WriteLine(input);
}
catch (Exception e) {
return GRLib.Exception.New(e.Message);
}
return null;
}
/// Returns error code 1 if timeout occured.
/// Timeout is represented in milliseconds.
/// Blocking call.
public GRLib.Exception GetOutput (out string output, int timeout = 2000) {
/// Wait for something to show in the queue.
/// Waits indefinitely if timeout is 0.
/// If anyone knows a better way to implement this waiting loop,
/// please let me know!
int timeWaited = 0;
while (outputQ.Count == 0) {
System.Threading.Thread.Sleep(100);
if (timeout != 0 && (timeWaited += 100) > timeout) {
output = "ERR";
return GRLib.Exception.New(1, "Get timed out.");
}
}
output = outputQ.Dequeue();
return null;
}
...
}
示例用法
Expression expression = new();
var e = expression.Run("3 > 2");
if (e != null) // Handle error
string output;
e = expression.GetOutput(out output);
if (e != null) // Handle error
// 'output' should now be 'true' which can then be used in other parts of this program.
虽然事件侦听器以独立的方式工作很好,但我需要在输入所在的同一个堆栈中返回来自流程的输出,因为这将是一个更复杂的调用图的一部分。
发布于 2022-07-13 05:44:09
您所观察到的问题是由于Process.StandardOutput.ReadToEnd()
的同步性质。相反,您应该通过设置Process.BeginOutputReadLine()
和利用Process.OutputDataReceived
事件异步地侦听输出。
下面是一个让您入门的快速示例:
var p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = @"ConsoleApplication1.exe";
p.OutputDataReceived += (s, e) =>
{
Console.WriteLine(e.Data);
};
p.Start();
p.BeginOutputReadLine();
while (true)
{
var readLine = Console.ReadLine();
p.StandardInput.WriteLine(readLine);
}
这是我用来做c++的ConsoleApplication1.exe
int main()
{
std::cout << "Hello World!\n";
std::string input;
while (std::getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
运行我的示例将打印Hello World!
,然后继续执行将您输入到控制台中的任何其他内容。
https://stackoverflow.com/questions/72960241
复制相似问题