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 что работает на HANDLEs. Таким образом, я мог бы использовать 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* - это просто записи в массиве файловой структуры внутри локального хранилища потоков.

Так что моя идея будет:

  1. откройте новый файл с помощью fopen. Теперь у нас есть новый файл* для массива внутренней структуры.
  2. Сохраните этот новый указатель в свой стек.
  3. теперь просто замените две структуры stdout новой файловой структурой.

код должен быть:

FILE swap = *stdout;
*stdout = *pFile;
*pFile = swap;

после этой операции дескриптор stdout теперь является новым файлом. Старый стандартный дескриптор out находится в файле * slow, сохраненном в стеке.

вернуться просто:

  1. получить файл+ из стека.
  2. снова замените stdout этим файлом*. (swap complete FILE structs)
  3. закройте файл*, который вы получили.

Если вы хотите сделать это с дескрипторами файлов, вам нужно связать дескриптор файла ОС с ПАПКА.* Это делается с помощью _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;
}