Как перенаправить стандартный вывод на какой-либо видимый экран в приложении Windows?
У меня есть доступ к сторонней библиотеке, которая делает "хорошие вещи". Он выдает статус и сообщения о прогрессе на стандартный вывод. В консольном приложении я вижу эти сообщения просто отлично. В приложениях 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 ответов
Вам нужно создать канал (с помощью CreatePipe ()), затем присоединить stdout к его концу записи с помощью SetStdHandle (), затем вы можете читать с конца чтения канала с помощью ReadFile() и помещать текст, который вы получите, куда угодно.
Вы можете перенаправить 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
Вы, вероятно, ищете что-то в этом роде:
#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
Когда вы создаете процесс с помощью CreateProcess(), вы можете выбрать HANDLE
на который будут записаны stdout и stderr. это HANDLE
может быть файл, к которому вы направляете вывод.
Это позволит вам использовать код без перекомпиляции. Просто выполните его и вместо использования system()
или еще много чего, используйте CreateProcess()
,
РУЧКА, которую вы даете CreateProcess()
Это также может быть канал, который вы создали, и затем вы можете читать из канала и делать с данными что-то еще.
Вы можете сделать что-то вроде этого с 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
,
Чтобы перенаправить стандартный вывод, вам нужно снова открыть дескриптор файла. В этой теме есть некоторые идеи такого рода.
Здесь мы установим новую точку входа consoleMain
это отменяет ваш собственный.
- Определите точку входа вашего приложения. В VisualStudio выберите " Свойства проекта" / "Связыватель" / "Дополнительно" / "Точка входа". Давайте называть это
defaultMain
, Где-то в вашем исходном коде объявляется исходная точка входа (чтобы мы могли с ней связываться) и новая точка входа. Оба должны быть объявлены
extern "C"
чтобы предотвратить искажение имени.extern "C" { int defaultMain (void); int consoleMain (void); }
Реализуйте функцию точки входа.
__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(); }
Добавьте свой тестовый код где-нибудь, например, в действии нажатия кнопки.
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);
- Задавать
consoleMain
в качестве новой точки входа (Project Properties / Linker / Advanced / Entry Point).
Вот что я бы сделал:
- CreatePipe().
- CreateProcess () с дескриптором из CreatePipe(), используемым как стандартный вывод для нового процесса.
- Создайте таймер или поток, который время от времени вызывает ReadFile() для этого дескриптора и помещает прочитанные данные в текстовое поле или еще много чего.
Благодаря ссылке на 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;
Это работает и подходит для отладки. Ввод текста в более привлекательный элемент графического интерфейса потребовал бы немного больше работы.