Есть ли хороший способ для потоковой передачи результатов из внешнего процесса в область вывода Visual Studio? - PullRequest
4 голосов
/ 01 декабря 2011

У меня есть пользовательская панель вывода, настроенная в VsPackage, аналогичная следующей:

    ///--------------------------------------------------------------------------------
    /// <summary>This property gets the custom output pane.</summary>
    ///--------------------------------------------------------------------------------
    private Guid _customPaneGuid = Guid.Empty;
    private IVsOutputWindowPane _customPane = null;
    private IVsOutputWindowPane customPane
    {
        get
        {
            if (_customPane == null)
            {
                IVsOutputWindow outputWindow = GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
                if (outputWindow != null)
                {
                    // look for existing solution updater pane
                    if (_customPaneGuid == Guid.Empty || ErrorHandler.Failed(outputWindow.GetPane(ref _customPaneGuid, out _customPane)) || _customPane == null)
                    {
                        // create a new solution updater pane
                        outputWindow.CreatePane(ref _customPaneGuid, "My Output", 1, 1);
                        if (ErrorHandler.Failed(outputWindow.GetPane(ref _customPaneGuid, out _customPane)) || _customPane == null)
                        {
                            // pane could not be created and retrieved, throw exception
                            throw new Exception("Custom pane could not be created and/or retrieved");
                        }
                    }
                }
            }
            if (_customPane != null)
            {
                _customPane.Activate();
            }
            return _customPane;
        }
    }

И сообщения отправляются на эту панель с помощью метода, аналогичного:

    ///--------------------------------------------------------------------------------
    /// <summary>This method displays a message in the output area.</summary>
    /// 
    /// <param name="outputTitle">The title for the message.</param>
    /// <param name="outputMessage">The message to show.</param>
    /// <param name="appendMessage">Flag indicating whether message should be appended to existing message.</param>
    ///--------------------------------------------------------------------------------
    public void ShowOutput(string outputTitle, string outputMessage, bool appendMessage, bool isException)
    {
        if (appendMessage == false)
        {
            // clear output pane
            CustomPane.Clear();
        }

        if (outputTitle != string.Empty)
        {
            // put output title to output pane
            CustomPane.OutputString("\r\n" + outputTitle);
        }

        // put output message to output pane
        CustomPane.OutputString("\r\n" + outputMessage);

        if (isException == true)
        {
            // show message box
            MessageBox.Show(outputTitle + "\r\n" + outputMessage, outputTitle);
        }
    }

У меня есть external process, который отправляет результаты диагностики текущего решения на консоль. Он настроен примерно так:

* * 1010

Когда я запускаю этот external process из VSPackage, я хотел бы направить эти результаты (косвенно) на пользовательскую панель вывода, показывая сообщения, когда средство диагностики сообщает о них. Есть ли хороший способ сделать это?

Ответы [ 4 ]

3 голосов
/ 01 декабря 2011

Очевидно, вы должны использовать метод Process.BeginOutputReadLine().Пример можно найти здесь: System.Diagnostics.Process.BeginOutputReadLine .

3 голосов
/ 01 декабря 2011

Обновлено RunDiagnostics, в основном использующее ответ Дж. Тихона и некоторые ответы the_drow:

///--------------------------------------------------------------------------------  
/// <summary>This method launches the diagnostics to review the solution.</summary>  
///--------------------------------------------------------------------------------  
protected void RunDiagnostics()  
{  
    try  
    {   
        // set up diagnostics process  
        string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName);  
        System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(@"MyDiagnostics.exe", solutionDir);  
        procStartInfo.RedirectStandardOutput = true;  
        procStartInfo.UseShellExecute = false;  
        procStartInfo.CreateNoWindow = true;  
        System.Diagnostics.Process proc = new System.Diagnostics.Process();  
        proc.StartInfo = procStartInfo;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine)
             => ShowOutput(String.Empty, outLine.Data, true, false);

        // execute the diagnostics  
        proc.Start();
        proc.BeginOutputReadLine();
    }  
    catch (Exception ex)  
    {  
        // put exception message in output pane  
        CustomPane.OutputString(ex.Message);  
    }  
}  
1 голос
/ 02 декабря 2011

Хотя OutPutDataReceived + BeginOutputReadLine выглядит как более элегантное и простое решение, я дам альтернативу.Я решил проблему с BackgroundWorker , и ProcessOutPutHandler вдохновил здесь .Этот подход также обрабатывает сообщения от stdout и stderr по отдельности, и я могу сообщать о прогрессе в BackgroundWorker в зависимости от вывода.Здесь я использую стандартное окно вывода VS для вывода, но оно также должно работать с вашим OutputPane:

public class ProcessOutputHandler
{
    public Process proc { get; set; }
    public string StdOut { get; set; }
    public string StdErr { get; set; }
    private IVsOutputWindowPane _pane;
    private BackgroundWorker _worker;

    /// <summary>  
    /// The constructor requires a reference to the process that will be read.  
    /// The process should have .RedirectStandardOutput and .RedirectStandardError set to true.  
    /// </summary>  
    /// <param name="process">The process that will have its output read by this class.</param>  
    public ProcessOutputHandler(Process process, BackgroundWorker worker)
    {
        _worker = worker;
        proc = process;
        IVsOutputWindow outputWindow =
        Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;

        Guid guidGeneral = Microsoft.VisualStudio.VSConstants.OutputWindowPaneGuid.GeneralPane_guid;
        int hr = outputWindow.CreatePane(guidGeneral, "Phone Visualizer", 1, 0);
        hr = outputWindow.GetPane(guidGeneral, out _pane);
        _pane.Activate();
        _pane.OutputString("Starting Ui State workers..");

        StdErr = "";
        StdOut = "";
        Debug.Assert(proc.StartInfo.RedirectStandardError, "RedirectStandardError must be true to use ProcessOutputHandler.");
        Debug.Assert(proc.StartInfo.RedirectStandardOutput, "RedirectStandardOut must be true to use ProcessOutputHandler.");
    }

    /// <summary>  
    /// This method starts reading the standard error stream from Process.  
    /// </summary>  
    public void ReadStdErr()
    {
        string line;
        while ((!proc.HasExited) && ((line = proc.StandardError.ReadLine()) != null))
        {
            StdErr += line;
            _pane.OutputString(line + "\n");
            // Here I could do something special if errors occur
        }
    }
    /// <summary>  
    /// This method starts reading the standard output sream from Process.  
    /// </summary>  
    public void ReadStdOut()
    {
        string line;
        while ((!proc.HasExited) && ((line = proc.StandardOutput.ReadLine()) != null))
        {
            StdOut += line;
            _pane.OutputString(line + "\n");
            if (_worker != null && line.Contains("Something I'm looking for"))
            {                            
               _worker.ReportProgress(20, "Something worth mentioning happened");
            }
        }
    }

}

И использование:

void RunProcess(string fileName, string arguments, BackgroundWorker worker)
{
  // prep process
  ProcessStartInfo psi = new ProcessStartInfo(fileName, arguments);
  psi.UseShellExecute = false;
  psi.RedirectStandardOutput = true;
  psi.RedirectStandardError = true;
  // start process
  using (Process process = new Process())
  {
    // pass process data
    process.StartInfo = psi;
    // prep for multithreaded logging
    ProcessOutputHandler outputHandler = new ProcessOutputHandler(process,worker);
    Thread stdOutReader = new Thread(new ThreadStart(outputHandler.ReadStdOut));
    Thread stdErrReader = new Thread(new ThreadStart(outputHandler.ReadStdErr));
    // start process and stream readers
    process.Start();
    stdOutReader.Start();
    stdErrReader.Start();
    // wait for process to complete
    process.WaitForExit();
   }
}

И это вызывается из BackgroundWorkerDoWork метод, рабочий передан в качестве ссылки.

1 голос
/ 01 декабря 2011

Вы можете использовать слушатель и присоединить к нему стандартный вывод процесса.

ConsoleTraceListener listener = new ConsoleTraceListener(process.StandardOutput);
Debug.Listeners.Add(listener);

Обязательно удалите его после завершения процесса:

proc.Exited += () => Debug.Listeners.Remove(listener);

Вам нужно будет использовать событие процесса * OutputDataReceived , а затем присоединить прослушиватель :

ConsoleTraceListener listener = new ConsoleTraceListener();
Debug.Listeners.Add(listener);

proc.OutputDataReceived += (object sendingProcess, DataReceivedEventArgs outLine) => Trace.WriteLine(outLine.Data);

Не забудьте удалить его после завершения процесса:

proc.Exited += (object sender, EventArgs e) => Debug.Listeners.Remove(listener);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...