onlyclxy 发表于 2024-9-24 03:22

问一个c#读取命令行程序不显示的问题

本帖最后由 onlyclxy 于 2024-9-25 18:32 编辑

这个问题已经解决.写作学习记录.
是python有个缓冲机制,缓冲是一种将输出暂时存储在内存中,然后批量写入到文件或屏幕的技术。当Python程序的输出被重定向到文件或管道时,通常会启用缓冲以提高性能,但这可能会导致输出的延迟或顺序混乱。
在Python中,-u选项用于在输出中禁用缓冲。使用-u选项可以禁用这种缓冲机制,即实时地将输出写入到文件或屏幕,从而确保输出立即显示。
经测试 python -u xxx.py 这个可以在c#实时读取
pyinstaller说明里也有个-u说是可以生成无缓冲文件.pyinstaller --onefile -u your_script.py
同时还有个写法
import sys
import time

print("Starting...", flush=True)# 添加 flush=True
while True:
    print("Running...", flush=True)
    time.sleep(1)



还有可以设置全局变量 set PYTHONUNBUFFERED=1 这个经测试对于python运行的有效 对于已经生成的exe无效


但是虽然说可以生成无缓冲的文件.但是对于别人的exe,还是做不到实时读取

然后又去问了群里的资深球佬.
球佬给我讲了个故事. 说以前有个群友需要用ssh做远程连接. 但是ssh那个需要输入一个密码. 那个密码不能说通过传参数的形式去传过去.只能是在那个框框去输入.
说你看包括vscode 或者 ide 里面都有那种框框. 那种你无论拖入什么程序,.他都可以去打印里面的应该打印的信息. 他那个东西叫伪终端.
他说他自己在unix上就搞了一套伪终端, 用这个伪终端解决了很多问题.
然后我很激动嘛 就去查这个伪终端. 查到一个WinPty的终端模拟器
就去网上搜, 搜到的项目貌似还需要我自己编译.. 我就去问gpt这个不是exe吗 ? gpt说你去下个git git里有.我寻思我有啊 一搜本地还真有
没想到这个winpry不光git有. vscode有,vsstudio有.anaconda里有.. 原来这个这么通用啊.
然后就尝试找c#去怎使用这个winpty.
最后结果发现我自己还是不太会弄这个winpty. 然后发现c#的库里有个现成的库winpty.Net
这个库我看解决了上面那个缓冲问题. 弄上后. 成功可以在c#输出python的实时消息了
我观察任务管理器里,他这个和那些一样. 会挂一个命令提示符,和打开的python文件

不过现在仍然有些小问题 就是framework下, 打印的字会有乱码. 我猜测可能是\r\n这种东西变成了乱码.但这个乱码同样的代码复制到.net6就不存在了.暂时不清楚原理.
为了能在framework用..暂时只能用替换的方法.把那些乱码替换掉了. 但是不确定是否还会有其他乱码. 如果有了解这块的大佬还请指教.
下面贴一下现在能跑的c#代码.
using System;
using System.IO;
using System.IO.Pipes;
using static winpty.WinPty;

class WinPtyExample
{
    static int Main(string[] args)
    {
      IntPtr handle = IntPtr.Zero;
      IntPtr err = IntPtr.Zero;
      IntPtr cfg = IntPtr.Zero;
      IntPtr spawnCfg = IntPtr.Zero;
      Stream stdin = null;
      Stream stdout = null;

      try
      {
            // 创建 WinPTY 配置
            cfg = winpty_config_new(WINPTY_FLAG_COLOR_ESCAPES, out err);
            winpty_config_set_initial_size(cfg, 80, 32);

            handle = winpty_open(cfg, out err);
            if (err != IntPtr.Zero)
            {
                Console.WriteLine($"Error opening WinPTY: {winpty_error_code(err)}");
                return 1;
            }

            // 指定要运行的 exe(cmd.exe)和参数(为空字符串)
            string exe = @"C:\Windows\System32\cmd.exe";
            string exeArgs = ""; // 无参数
            string cwd = @"C:\"; // 工作目录

            // 创建进程配置
            spawnCfg = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, exe, exeArgs, cwd, null, out err);
            if (err != IntPtr.Zero)
            {
                Console.WriteLine($"Error creating spawn config: {winpty_error_code(err)}");
                return 1;
            }

            // 创建输入和输出管道
            stdin = CreatePipe(winpty_conin_name(handle), PipeDirection.Out);
            stdout = CreatePipe(winpty_conout_name(handle), PipeDirection.In);

            // 启动进程
            if (!winpty_spawn(handle, spawnCfg, out IntPtr process, out IntPtr thread, out int procError, out err))
            {
                Console.WriteLine($"Spawn failed with error code: {procError}"); // 输出错误代码
                Console.WriteLine($"Error during spawn: {winpty_error_code(err)}");
                return 1;
            }

            // 读取输出
            using (var reader = new StreamReader(stdout))
            {
                string outputLine;
                while ((outputLine = reader.ReadLine()) != null)
                {
                  Console.WriteLine(outputLine);
                }
            }

            return 0;
      }
      finally
      {
            stdin?.Dispose();
            stdout?.Dispose();
            winpty_config_free(cfg);
            winpty_spawn_config_free(spawnCfg);
            winpty_error_free(err);
            winpty_free(handle);
      }
    }

    private static Stream CreatePipe(string pipeName, PipeDirection direction)
    {
      string serverName = ".";
      if (pipeName.StartsWith("\\"))
      {
            int slash3 = pipeName.IndexOf('\\', 2);
            if (slash3 != -1)
            {
                serverName = pipeName.Substring(2, slash3 - 2);
            }
            int slash4 = pipeName.IndexOf('\\', slash3 + 1);
            if (slash4 != -1)
            {
                pipeName = pipeName.Substring(slash4 + 1);
            }
      }

      var pipe = new NamedPipeClientStream(serverName, pipeName, direction);
      pipe.Connect();
      return pipe;
    }
}

下面是暂时的成品界面版的.
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using static winpty.WinPty;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
      private TextBox outputTextBox;
      private Button runButton;

      public Form1()
      {
            InitializeComponent();
            InitializeControls();
      }

      private void InitializeControls()
      {
            outputTextBox = new TextBox
            {
                Multiline = true,
                Dock = DockStyle.Fill,
                ScrollBars = ScrollBars.Both,
                Font = new System.Drawing.Font("Consolas", 10)
            };
            this.Controls.Add(outputTextBox);

            runButton = new Button
            {
                Text = "运行命令",
                Dock = DockStyle.Top
            };
            runButton.Click += runButton_Click;
            this.Controls.Add(runButton);

            this.Text = "WinPty 示例";
            this.Size = new System.Drawing.Size(800, 600);
      }

      private async void runButton_Click(object sender, EventArgs e)
      {
            outputTextBox.Clear();
            await Task.Run(() => RunExecutable());
      }

      private void RunExecutable()
      {
            IntPtr handle = IntPtr.Zero;
            IntPtr err = IntPtr.Zero;
            IntPtr cfg = IntPtr.Zero;
            IntPtr spawnCfg = IntPtr.Zero;
            Stream stdin = null;
            Stream stdout = null;

            try
            {
                cfg = winpty_config_new(WINPTY_FLAG_COLOR_ESCAPES, out err);
                winpty_config_set_initial_size(cfg, 80, 32);

                handle = winpty_open(cfg, out err);
                if (err != IntPtr.Zero)
                {
                  UpdateOutput($"Error opening WinPTY: {winpty_error_code(err)}");
                  return;
                }
               
                string exe = @"C:\Windows\System32\cmd.exe";
                //exe = @"C:\死循环测试.exe";
                string exeArgs = "";
                string cwd = @"C:\";

                spawnCfg = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, exe, exeArgs, cwd, null, out err);
                if (err != IntPtr.Zero)
                {
                  UpdateOutput($"Error creating spawn config: {winpty_error_code(err)}");
                  return;
                }

                stdin = CreatePipe(winpty_conin_name(handle), PipeDirection.Out);
                stdout = CreatePipe(winpty_conout_name(handle), PipeDirection.In);

                if (!winpty_spawn(handle, spawnCfg, out IntPtr process, out IntPtr thread, out int procError, out err))
                {
                  UpdateOutput($"Spawn failed with error code: {procError}");
                  UpdateOutput($"Error during spawn: {winpty_error_code(err)}");
                  return;
                }

                using (var reader = new StreamReader(stdout, Encoding.UTF8))
                {
                  string outputLine;
                  while ((outputLine = reader.ReadLine()) != null)
                  {
                        //string cleanOutput = RemoveSpecialCharacters(outputLine);
                        //cleanOutput = RemoveSpecificCharacters(cleanOutput);
                        outputLine = CleanUp(outputLine);
                        UpdateOutput(outputLine);
                  }
                }
            }
            finally
            {
                stdin?.Dispose();
                stdout?.Dispose();
                winpty_config_free(cfg);
                winpty_spawn_config_free(spawnCfg);
                winpty_error_free(err);
                winpty_free(handle);
            }
      }

      private string RemoveSpecialCharacters(string input)
      {
            // 过滤掉 ANSI 转义序列和其他特定控制字符
            string pattern = @"(\x1B\[*|(\x1B\]0;.*?\x07)|[\x07])";
            string cleanedInput = Regex.Replace(input, pattern, string.Empty);

            // 过滤掉特殊字符、ANSI 转义序列和控制字符
             pattern = @"(\x1B\[*|[\x07])"; // 匹配 ANSI 转义序列
             cleanedInput = Regex.Replace(cleanedInput, pattern, string.Empty);


            // 去掉行首和行尾的空白字符
            return cleanedInput.Trim();
      }

      public static string CleanUp(string input)
      {
            // 移除第一组字符
            input = input.Replace("\x1B[0m", ""); // 删除字符:  (ASCII: 27) + [ + 0 + m
            input = input.Replace("\x1B[0K", ""); // 删除字符:  (ASCII: 27) + [ + 0 + K

            // 移除第二组字符
            input = input.Replace("\x1B[?25l", ""); // 删除字符:  (ASCII: 27) + [ + ? + 2 + 5 + l

            // 移除第三组字符
            input = input.Replace("\x1B[?25h", ""); // 删除字符:  (ASCII: 27) + [ + ? + 2 + 5 + h

            // 移除第四组字符
            input = input.Replace("\x1B]0;", ""); // 删除字符:  (ASCII: 27) + ] + 0 + ;

            // 移除第五组字符
            input = input.Replace("\a", ""); // 删除字符:  (ASCII: 7)
            input = input.Replace("\x1B[0m", ""); // 删除字符:  (ASCII: 27) + [ + 0 + m


            input = input.Replace("\r\n", "\n") // 统一换行符
            .Replace("\n\n", "\n"); // 删除连续空行
            while (input.Contains("\n\n")) // 循环直到没有连续空行
            {
                input = input.Replace("\n\n", "\n");
            }
            if (input.StartsWith("\n"))
            {
                input = input.Substring(1); // 删除开头的换行符
            }


            return input;
      }




      private void UpdateOutput(string message)
      {
            if (outputTextBox.InvokeRequired)
            {
                outputTextBox.Invoke(new Action<string>(UpdateOutput), message);
            }
            else
            {
                outputTextBox.AppendText(message + Environment.NewLine);
            }
      }

      private Stream CreatePipe(string pipeName, PipeDirection direction)
      {
            string serverName = ".";
            if (pipeName.StartsWith("\\"))
            {
                int slash3 = pipeName.IndexOf('\\', 2);
                if (slash3 != -1)
                {
                  serverName = pipeName.Substring(2, slash3 - 2);
                }
                int slash4 = pipeName.IndexOf('\\', slash3 + 1);
                if (slash4 != -1)
                {
                  pipeName = pipeName.Substring(slash4 + 1);
                }
            }

            var pipe = new NamedPipeClientStream(serverName, pipeName, direction);
            pipe.Connect();
            return pipe;
      }
    }
}

https://pic.imgdb.cn/item/66f3e654f21886ccc0dfdc5c.png


求助各位大佬 有点想不通
本来那种命令行的exe.可以通过cmd打开.
我想着用c#写一个, 也是获取这种命令行的exe 然后输出到编辑框里
结果写到一半发现这个c#无法获取我用python写的一个东西,打印都打印不出来
python是写了一个死循环,生成了个exe
import time

def main():
    start_time = time.time()# 获取脚本开始运行的时间
    while True:
      elapsed_time = time.time() - start_time# 计算已过时间
      print(f"这是一个测试循环,已运行 {int(elapsed_time)} 秒")
      time.sleep(1)# 暂停一秒

if __name__ == "__main__":
    main()

然后c#这边写了个异步读取.现在连打印都不打印这个,就卡在第一行里
using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
      // 输入 exe 文件的路径
      Console.WriteLine("请输入 exe 文件的路径:");
      string exePath = Console.ReadLine();

      // 检查路径是否为空或无效
      if (string.IsNullOrWhiteSpace(exePath))
      {
            Console.WriteLine("请输入有效的路径。");
            return;
      }

      try
      {
            // 异步捕获并输出
            await RunProcessAsync(exePath);
      }
      catch (Exception ex)
      {
            Console.WriteLine($"运行过程中发生错误: {ex.Message}");
      }
    }

    static async Task RunProcessAsync(string exePath)
    {
      var process = new Process
      {
            StartInfo = new ProcessStartInfo
            {
                FileName = exePath,         // EXE 路径
                RedirectStandardOutput = true, // 重定向标准输出
                RedirectStandardError = true,// 重定向标准错误
                UseShellExecute = false,       // 必须禁用 Shell 执行以重定向流
                CreateNoWindow = true          // 禁止创建窗口
            },
            EnableRaisingEvents = true // 启用事件以便异步通知
      };

      // 当输出时触发事件
      process.OutputDataReceived += (sender, e) =>
      {
            if (!string.IsNullOrEmpty(e.Data))
            {
                Console.WriteLine(e.Data); // 打印标准输出的每一行
            }
      };

      // 当错误输出时触发事件
      process.ErrorDataReceived += (sender, e) =>
      {
            if (!string.IsNullOrEmpty(e.Data))
            {
                Console.Error.WriteLine(e.Data); // 打印标准错误的每一行
            }
      };

      // 异步启动进程
      process.Start();

      // 异步读取输出和错误流
      process.BeginOutputReadLine();
      process.BeginErrorReadLine();

      // 等待进程完成
      await process.WaitForExitAsync();

      Console.WriteLine($"进程结束,退出代码: {process.ExitCode}");
    }
}

界面的:
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace 命令行界面
{
    public partial class Form1 : Form
    {
      private string programPath = "C:\\死循环测试.exe";// 这里假设程序路径已固定,您可以根据实际情况修改

      public Form1()
      {
            InitializeComponent();
      }

      private async Task button1_Click(object sender, EventArgs e)
      {
            await ExecuteCommandAsync(programPath);

      }

      private async Task ExecuteCommandAsync(string command)
      {
            // 设置为启动cmd.exe,并通过参数传递要执行的命令
            ProcessStartInfo startInfo = new ProcessStartInfo
            {
                FileName = "cmd.exe",// 使用cmd.exe作为启动程序
                Arguments = $"/c {command}",// /c 表示执行字符串指定的命令然后结束
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true, // 同时捕获错误输出
                CreateNoWindow = true
            };

            using (Process process = new Process { StartInfo = startInfo })
            {
                process.Start();

                // 异步读取标准输出
                while (!process.StandardOutput.EndOfStream)
                {
                  string line = await process.StandardOutput.ReadLineAsync();
                  UpdateTextBox(line);
                }

                // 异步读取标准错误输出
                while (!process.StandardError.EndOfStream)
                {
                  string errorLine = await process.StandardError.ReadLineAsync();
                  UpdateTextBox("Error: " + errorLine);// 可以区分错误信息
                }
            }
      }

      private void UpdateTextBox(string text)
      {
            if (textBoxOutput.InvokeRequired)
            {
                textBoxOutput.Invoke(new MethodInvoker(delegate
                {
                  textBoxOutput.AppendText(text + Environment.NewLine);
                }));
            }
            else
            {
                textBoxOutput.AppendText(text + Environment.NewLine);
            }
      }
    }
}


我感觉不应该啊 这种东西实现不了吗gpt出了一个晚上也没搞定.
求助各位大佬

only998 发表于 2024-9-24 05:44

本帖最后由 only998 于 2024-9-24 07:07 编辑

写的太绕了,试试直接在c#的读取exe上开一个线程来运行python的exe,不需要通过cmd   xxxx.exe来执行。

我试了一下,写死循环无法获得任何输出,估计是python的exe一直在线程中无法处理其他事件。

vscos 发表于 2024-9-24 06:28

你那个EXE最好不要有死循环,

不知道改成啥 发表于 2024-9-24 08:48

休眠1毫秒试试看呢

flyer_2001 发表于 2024-9-24 10:19


import asyncio

async def my_coroutine():
    print(“协程开始执行”)
    await asyncio.sleep(5)
    print(“协程继续执行”)

asyncio.run(my_coroutine())
sleep是堵塞的,使用异步试试

onlyclxy 发表于 2024-9-24 17:52

不知道改成啥 发表于 2024-9-24 08:48
休眠1毫秒试试看呢

休眠哪个啊?

onlyclxy 发表于 2024-9-24 17:59

vscos 发表于 2024-9-24 06:28
你那个EXE最好不要有死循环,

python肯定要死循环的. 服务类的东西都是不手动关,就不自动停止
我倒是怀疑是python的exe有毛病,,,
这边做的主要是c#读取exe的输出信息. 不限定什么exe. 不能对exe有要求..

onlyclxy 发表于 2024-9-24 18:09

flyer_2001 发表于 2024-9-24 10:19

import asyncio



感谢大佬!不过我本质不是要对python有要求。是看看c#这边能否实现这种效果
这个背景是当时python不方便生成界面。 前一阵突然想到是否可以直接用c#写一个界面? 然后直接获取python的exe输出,打印到c#的编辑框里。 因为c#这边很容易做界面。那些python原来写的exe,过于复杂。 所以先拿个简单的python做测试。 所以不能说把python以前的代码改了。

onlyclxy 发表于 2024-9-24 18:31

flyer_2001 发表于 2024-9-24 10:19

import asyncio



试了试这个异步的..python这几个都不好使..

不知道改成啥 发表于 2024-9-24 21:23

onlyclxy 发表于 2024-9-24 17:52
休眠哪个啊?

python里面啊
页: [1] 2
查看完整版本: 问一个c#读取命令行程序不显示的问题