Как перенаправить стандартный вывод на какой-либо видимый экран в приложении Windows? - PullRequest
37 голосов
/ 21 февраля 2009

У меня есть доступ к сторонней библиотеке, которая делает "хорошие вещи". Он выдает статус и сообщения о прогрессе на стандартный вывод. В консольном приложении я вижу эти сообщения просто отлично. В Windows-приложениях они просто переходят в разрядное ведро.

Есть ли довольно простой способ перенаправить stdout и stderr в текстовый элемент управления или другое видимое место. В идеале это не потребует перекомпиляции стороннего кода. Было бы просто перехватить пары на низком уровне. Мне бы хотелось решение, в котором я бы просто #include заголовок, вызвать функцию инициализации и связать библиотеку как в ...

#include "redirectStdFiles.h"

void function(args...)
{
  TextControl* text = new TextControl(args...);
  initializeRedirectLibrary(text, ...);

  printf("Message that will show up in the TextControl\n");
  std::cout << "Another message that also shows up in TextControl\n";
}

Еще лучше было бы, если бы он использовал некоторый интерфейс, который я мог бы переопределить, чтобы он не был привязан к какой-либо конкретной библиотеке GUI.

class StdFilesRedirector
{
  public:
    writeStdout(std::string const& message) = 0;
    writeStderr(std::string const& errorMessage) = 0;
    readStdin(std::string &putReadStringHere) = 0;
};

Я просто сплю? Или кто-нибудь знает что-то, что может сделать что-то подобное?

Правка после двух ответов: я думаю, что использование freopen для перенаправления файлов - хороший первый шаг. Для полного решения должен быть создан новый поток, чтобы прочитать файл и отобразить вывод. Для отладки было бы достаточно выполнить tail -f в окне оболочки cygwin. Для более отточенного приложения ... Что я хочу написать ... была бы дополнительная работа по созданию потока и т. Д.

Ответы [ 8 ]

18 голосов
/ 22 февраля 2009

Вам нужно создать канал (с CreatePipe () ), затем присоединить stdout к концу записи с помощью SetStdHandle () , затем вы можете прочитать с конца чтения канала с помощью ReadFile () и поместите текст, который вы получаете оттуда, где вам нравится.

16 голосов
/ 21 февраля 2009

Вы можете перенаправить stdout, stderr и stdin, используя freopen .

Из приведенной выше ссылки:

/* freopen example: redirecting stdout */
#include <stdio.h>

int main ()
{
  freopen ("myfile.txt","w",stdout);
  printf ("This sentence is redirected to a file.");
  fclose (stdout);
  return 0;
}

Вы также можете запустить вашу программу из командной строки, например:

a.exe > stdout.txt 2> stderr.txt
14 голосов
/ 06 марта 2009

Вы, вероятно, ищете что-то в этом роде:

    #define OUT_BUFF_SIZE 512

    int main(int argc, char* argv[])
    {
        printf("1: stdout\n");

        StdOutRedirect stdoutRedirect(512);
        stdoutRedirect.Start();
        printf("2: redirected stdout\n");
        stdoutRedirect.Stop();

        printf("3: stdout\n");

        stdoutRedirect.Start();
        printf("4: redirected stdout\n");
        stdoutRedirect.Stop();

        printf("5: stdout\n");

        char szBuffer[OUT_BUFF_SIZE];
        int nOutRead = stdoutRedirect.GetBuffer(szBuffer,OUT_BUFF_SIZE);
        if(nOutRead)
            printf("Redirected outputs: \n%s\n",szBuffer);

        return 0;
    }

Этот класс сделает это:

#include <windows.h>

#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>

#ifndef _USE_OLD_IOSTREAMS
using namespace std;
#endif

#define READ_FD 0
#define WRITE_FD 1

#define CHECK(a) if ((a)!= 0) return -1;

class StdOutRedirect
{
    public:
        StdOutRedirect(int bufferSize);
        ~StdOutRedirect();

        int Start();
        int Stop();
        int GetBuffer(char *buffer, int size);

    private:
        int fdStdOutPipe[2];
        int fdStdOut;
};

StdOutRedirect::~StdOutRedirect()
{
    _close(fdStdOut);
    _close(fdStdOutPipe[WRITE_FD]);
    _close(fdStdOutPipe[READ_FD]);
}
StdOutRedirect::StdOutRedirect(int bufferSize)
{
    if (_pipe(fdStdOutPipe, bufferSize, O_TEXT)!=0)
    {
        //treat error eventually
    }
    fdStdOut = _dup(_fileno(stdout));
}

int StdOutRedirect::Start()
{
    fflush( stdout );
    CHECK(_dup2(fdStdOutPipe[WRITE_FD], _fileno(stdout)));
    ios::sync_with_stdio();
    setvbuf( stdout, NULL, _IONBF, 0 ); // absolutely needed
    return 0;
}

int StdOutRedirect::Stop()
{
    CHECK(_dup2(fdStdOut, _fileno(stdout)));
    ios::sync_with_stdio();
    return 0;
}

int StdOutRedirect::GetBuffer(char *buffer, int size)
{
    int nOutRead = _read(fdStdOutPipe[READ_FD], buffer, size);
    buffer[nOutRead] = '\0';
    return nOutRead;
}

Вот результат:

1: stdout
3: stdout
5: stdout
Redirected outputs:
2: redirected stdout
4: redirected stdout
5 голосов
/ 22 февраля 2009

Когда вы создаете процесс, используя CreateProcess () , вы можете выбрать HANDLE, в который будут записываться stdout и stderr. Этот HANDLE может быть файлом, в который вы направляете вывод.

Это позволит вам использовать код без перекомпиляции. Просто выполните его и вместо system() или еще чего-нибудь используйте CreateProcess().

РУЧКА, которую вы даете CreateProcess(), может быть также и трубой, которую вы создали, и затем вы можете читать из трубы и делать что-то еще с данными.

3 голосов
/ 22 февраля 2009

Вы можете сделать что-то подобное с cout или cerr:

// open a file stream
ofstream out("filename");
// save cout's stream buffer
streambuf *sb = cout.rdbuf();
// point cout's stream buffer to that of the open file
cout.rdbuf(out.rdbuf());
// now you can print to file by writing to cout
cout << "Hello, world!";
// restore cout's buffer back
cout.rdbuf(sb);

Или вы можете сделать это с std::stringstream или другим классом, производным от std::ostream.

Чтобы перенаправить стандартный вывод, вам нужно снова открыть дескриптор файла. В этой теме есть некоторые идеи такого рода.

2 голосов
/ 26 ноября 2015

Здесь мы установим новую точку входа consoleMain, которая переопределяет вашу собственную.

  1. Определите точку входа вашего приложения. В VisualStudio выберите Свойства проекта / Компоновщик / Дополнительно / Точка входа . Давайте назовем это defaultMain.
  2. Где-то в вашем исходном коде объявляется исходная точка входа (чтобы мы могли с ней связываться) и новая точка входа. Оба должны быть объявлены extern "C", чтобы предотвратить искажение имени.

    extern "C"
    {
      int defaultMain (void);
      int consoleMain (void);
    }
    
  3. Реализация функции точки входа.

    __declspec(noinline) int consoleMain (void)
    {
      // __debugbreak(); // Break into the program right at the entry point!
      AllocConsole();    // Create a new console
      freopen("CON", "w", stdout);
      freopen("CON", "w", stderr);
      freopen("CON", "r", stdin); // Note: "r", not "w".
      return defaultMain();
    }
    
  4. Добавьте где-нибудь свой тестовый код, например, в действии нажатия кнопки.

    fwprintf(stdout, L"This is a test to stdout\n");
    fwprintf(stderr, L"This is a test to stderr\n");
    cout<<"Enter an Integer Number Followed by ENTER to Continue" << endl;
    _flushall();
    int i = 0;
    int Result = wscanf( L"%d", &i);
    printf ("Read %d from console. Result = %d\n", i, Result);
    
  5. Установить consoleMain в качестве новой точки входа ( Свойства проекта / Компоновщик / Дополнительно / Точка входа ).
1 голос
/ 23 февраля 2009

Благодаря ссылке gamedev в ответе greyfade я смог написать и протестировать этот простой фрагмент кода

AllocConsole();

*stdout = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_OUTPUT_HANDLE), _O_WRONLY), _T("a"));
*stderr = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_ERROR_HANDLE), _O_WRONLY), _T("a"));
*stdin = *_tfdopen(_open_osfhandle((intptr_t) GetStdHandle(STD_INPUT_HANDLE), _O_WRONLY), _T("r"));


printf("A printf to stdout\n");
std::cout << "A << to std::cout\n";
std::cerr << "A << to std::cerr\n";
std::string input;
std::cin >> input;

std::cout << "value read from std::cin is " << input << std::endl;

Работает и подходит для отладки. Ввод текста в более привлекательный элемент графического интерфейса потребовал бы немного больше работы.

1 голос
/ 22 февраля 2009

Вот что я бы сделал:

  1. CreatePipe ().
  2. CreateProcess () с дескриптором из CreatePipe (), используемым как стандартный вывод для нового процесса.
  3. Создайте таймер или поток, который время от времени вызывает ReadFile () для этого дескриптора и помещает прочитанные данные в текстовое поле или еще много чего.
...