SetStdHandle не влияет на cout/printf
название говорит само за себя. Когда я запускаю следующий код:
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hFile = CreateFile(TEXT("Foo.txt"), GENERIC_WRITE, FILE_READ_ACCESS | FILE_WRITE_ACCESS,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
SetStdHandle(STD_OUTPUT_HANDLE, hFile);
std::cout << "Hello, ";
printf("world!n");
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello, world!n", 13, NULL, NULL);
SetStdHandle(STD_OUTPUT_HANDLE, hOut);
CloseHandle(hFile);
в результате Hello, world!
записывается на консоль в результате звонки cout
и printf
и Hello, world!
также записывается в файл Foo.txt
в результате вызова WriteFile
. Я предполагаю, что когда все инициализируется в самом начале,HANDLE
возвращено GetStdHandle
кэшируется и использовать cout
и printf
. Это совершенно разумно и точно что я хотел бы, как я предполагаю GetStdHandle
требуется вызов операционной системы (который может быть длинным!). Проблема в том, что я хочу переопределить это поведение и "синхронизировать" как cout, так и printf со стандартными дескрипторами приложения, если это возможно.
прежде чем предлагать какие-либо альтернативы, позвольте мне описать, что именно я пытаюсь сделать (да, я знаю, что можно использовать freopen
для этой цели). Что мне нужно сделать, так это "сохранить" текущий стандартный выходной дескриптор на структура данных, подобная стеку, прежде чем я изменю ее, чтобы я мог восстановить предыдущий дескриптор вывода. Все, кроме этого, неприемлемо для этой ситуации (т. е. я не могу восстановить CONOUT$
, etc.). Это должно иметь способность быть рекурсивным. Т. е. следующее должно работать так, как вы ожидаете:
std::cout << "A1" << std::endl;
StartStdOutRedirection(TEXT("Foo.txt"));
std::cout << "B1" << std::endl;
StartStdOutRedirection(TEXT("Bar.txt"));
std::cout << "C1" << std::endl;
EndStdOutRedirection();
std::cout << "B2" << std::endl;
EndStdOutRedirection();
std::cout << "A2" << std::endl;
это было бы слишком легко, если есть способ, чтобы "синхронизировать" stdout
как следующий код должен делать свое дело:
std::vector<HANDLE> vStdOutHandles;
void StartStdOutRedirection(_In_ LPCTSTR lpFile)
{
vStdOutHandles.push_back(GetStdHandle(STD_OUTPUT_HANDLE));
SetStdHandle(STD_OUTPUT_HANDLE, CreateFile(lpFile, GENERIC_WRITE,
FILE_WRITE_ACCESS | FILE_READ_ACCESS, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
}
void EndStdOutRedirection(void)
{
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
SetStdHandle(STD_OUTPUT_HANDLE, vStdOutHandles.back());
vStdOutHandles.pop_back();
}
в правильность приведенного выше кода можно проверить с помощью WriteFile
С GetStdHandle(STD_OUTPUT_HANDLE)
на месте cout
. В идеале мне нужен эквивалент freopen
что работает на HANDLE
s. Таким образом, я мог бы использовать DuplicateHandle
на HANDLE
возвращено GetStdHandle
и после этого MyReopenHandle
функция для установки базового файла для этого HANDLE
к файлу по моему вкусу. Я считаю, что это будет работать, поскольку я предполагаю, что оба printf
и cout
есть HANDLE
сохранено где-то глубоко внутри. Я пытался "притвориться" дублирование стандартного дескриптора вывода, закрытие этого дескриптора, а затем вызов CreateFile
в надежде, что он даст мне тот же HANDLE
значением, но это тока. Вот мой код для этого, если вам интересно:
std::vector<HANDLE> vStdOutHandles;
bool StartStdOutRedirection(_In_ LPCTSTR lpFile)
{
bool fResult = false;
HANDLE hProc = GetCurrentProcess();
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut != INVALID_HANDLE_VALUE)
{
HANDLE hDup;
if (DuplicateHandle(hProc, hOut, hProc, &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
// Need to close the current handle before we open the new one
CloseHandle(hOut);
HANDLE hFile = CreateFile(lpFile, GENERIC_WRITE, FILE_WRITE_ACCESS | FILE_READ_ACCESS,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
// Should be same HANDLE; else we're screwed...
assert(hFile == hOut);
SetStdHandle(STD_OUTPUT_HANDLE, hFile);
vStdOutHandles.push_back(hDup);
fResult = true;
}
else
{
// Otherwise, reopen the previous output HANDLE on failure
DuplicateHandle(hProc, hDup, hProc, &hFile, 0, FALSE, DUPLICATE_SAME_ACCESS);
assert(hFile == hOut);
CloseHandle(hDup);
}
}
}
return fResult;
}
bool EndStdOutRedirection(void)
{
bool fResult = false;
HANDLE hProc = GetCurrentProcess();
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut != INVALID_HANDLE_VALUE && vStdOutHandles.size() != 0)
{
HANDLE hDup;
HANDLE hNext = vStdOutHandles.back();
// Close current handle and re-open previous one
CloseHandle(hOut);
if (DuplicateHandle(hProc, hNext, hProc, &hDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
// Again, we're screwed if these are not the same
assert(hOut == hDup);
SetStdHandle(STD_OUTPUT_HANDLE, hDup);
vStdOutHandles.pop_back();
fResult = true;
}
}
return fResult;
}
вышеуказанное утверждение терпит неудачу примерно в половине случаев (я не ожидал или не рассчитывал на то, что это сработает... Мне просто было интересно). Это примерно то, что я получил с точки зрения этой проблемы. Если у кого-нибудь есть предложения, пожалуйста, дайте мне знать :)
3 ответов
Вау, через некоторое время поиска способа вручную установить HANDLE
на FILE
, Я, наконец, обнаружил, что есть довольно простой способ сделать это с помощью библиотеки времени выполнения C:
std::vector<int> vfdStdOut;
void StartStdOutRedirection(_In_ LPCSTR lpFile)
{
// Duplicate stdout and give it a new file descriptor
int fdDup = _dup(_fileno(stdout));
vfdStdOut.push_back(fdDup);
// Re-open stdout to the new file
freopen(lpFile, "w", stdout);
}
bool EndStdOutRedirection(void)
{
if (vfdStdOut.size() != 0)
{
// Get last saved file descriptor and restore it
int fdNext = vfdStdOut.back();
_dup2(fdNext, _fileno(stdout));
// Need to close the file associated with the saved file descriptor
_close(fdNext);
vfdStdOut.pop_back();
return true;
}
return false;
}
Это также позаботится о вызове SetStdHandle
для вас!
Это будет работать только для MS-CRT.
FILE* - это просто записи в массиве файловой структуры внутри локального хранилища потоков.
Так что моя идея будет:
- откройте новый файл с помощью fopen. Теперь у нас есть новый файл* для массива внутренней структуры.
- Сохраните этот новый указатель в свой стек.
- теперь просто замените две структуры stdout новой файловой структурой.
код должен быть:
FILE swap = *stdout;
*stdout = *pFile;
*pFile = swap;
после этой операции дескриптор stdout теперь является новым файлом. Старый стандартный дескриптор out находится в файле * slow, сохраненном в стеке.
вернуться просто:
- получить файл+ из стека.
- снова замените stdout этим файлом*. (swap complete FILE structs)
- закройте файл*, который вы получили.
Если вы хотите сделать это с дескрипторами файлов, вам нужно связать дескриптор файла ОС с ПАПКА.* Это делается с помощью _open_osfhandle () и _fdopen ().
поскольку операции с файлами используют буфер,необходимо очистить буферы перед заменой. Чтобы убедиться, что нет" остатков " от старого вывода.
С моей точки зрения, этот хак должен работать.
вот решение, которое я собрал (далеко не идеально, конечно). Он вызывает пользовательскую функцию для каждого символа, записанного в STDOUT. В моем примере он перенаправляет поток на вызовы OutputDebugString.
#include <windows.h>
#include <io.h>
#include <functional>
#include <iostream>
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
enum StdHandleToRedirect {
STDOUT, STDERR
};
class StdRedirect {
public:
/// Assumes the specified handle is still assigned to the default FILENO (STDOUT_FILENO/STDERR_FILENO)
/// TODO allow redirection in every case
/// callback will run in a new thread and will be notified of any character input to
/// the specified std handle
StdRedirect(StdHandleToRedirect h, std::function<void(char)> callback) : callback(callback) {
CreatePipe(&readablePipeEnd, &writablePipeEnd, 0, 0);
SetStdHandle(h == STDOUT ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE, writablePipeEnd);
// Redirect (TODO: ERROR CHECKING)
int writablePipeEndFileStream = _open_osfhandle((long)writablePipeEnd, 0);
FILE* writablePipeEndFile = NULL;
writablePipeEndFile = _fdopen(writablePipeEndFileStream, "wt");
_dup2(_fileno(writablePipeEndFile), h == STDOUT ? STDOUT_FILENO : STDERR_FILENO);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)stdreader, this, 0, 0);
}
// TODO implement destructor, cleanup, reset
private:
// DWORD (WINAPI *PTHREAD_START_ROUTINE)(LPVOID lpThreadParameter)
static void WINAPI stdreader(StdRedirect* redirector) {
while (1) {
char c;
DWORD read;
::fflush(NULL); // force current stdout to become readable
// TODO add error handling
ReadFile(redirector->readablePipeEnd, (void*)&c, 1, &read, 0); // this blocks until input is available
if (read == 1)
redirector->callback(c);
}
}
HANDLE readablePipeEnd, writablePipeEnd;
const std::function<void(char)> callback;
};
int main() {
std::function<void(char)> toOutputDebugString = [](char x) {
char str[2] = {x, 0};
OutputDebugStringA(str);
};
StdRedirect so(STDOUT, toOutputDebugString);
std::cout << "test stdout\n";
while (1); // busy loop to give the thread time to read stdout.
// You might want to look at "Output: Show output from: Debug" now.
return 0;
}