SetStdHandle对cout / printf没有影响

时间:2014-01-27 06:45:17

标签: windows winapi visual-c++

标题说明了一切。当我运行以下代码时:

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!因调用coutprintf而被写入控制台,而Hello, world!也会被写入文件{{ 1}}是对Foo.txt的调用的结果。我的假设是,当一切都在一开始就被初始化时,WriteFile返回的HANDLE被缓存并重新用于GetStdHandlecout。这是完全合理的,正是我想要的,因为我认为printf需要调用操作系统(可能很长!)。麻烦的是我想要覆盖这种行为并尽可能地将cout和printf与应用程序的标准句柄“同步”。

在提出任何替代方案之前,让我准确描述一下我正在尝试做什么(是的,我知道可以为此目的使用GetStdHandle。我需要做的是在更改之前将当前标准输出句柄“保存”在类似堆栈的数据结构上,以便我能够恢复以前的输出句柄。任何不足之处都是不可接受的(即我无法恢复到freopen等)。这需要具有递归的能力。即以下应该按照您的预期工作:

CONOUT$

如果有一种“重新同步”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的{​​{1}}。这样我可以在HANDLE返回的DuplicateHandle上使用HANDLE,然后使用此GetStdHandle函数将MyReopenHandle的基础文件设置为我的文件喜好。我相信这会有效,因为我认为HANDLEprintf都有cout保存在内心深处。我试图通过复制标准输出句柄,关闭该句柄,然后调用HANDLE来试图“伪造它”,希望它能给我相同的CreateFile值,但最多只能偶尔使用。如果您有兴趣,这是我的代码:

HANDLE

上述断言失败了大约一半的时间(我并没有真正期待或指望它起作用......我只是感兴趣)。就这个问题而言,这就是我所知道的。如果有人有任何建议,请告诉我:))

3 个答案:

答案 0 :(得分:2)

哇,经过一段时间寻找手动设置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

答案 1 :(得分:0)

这仅适用于MS-CRT。

FILE *只是线程本地存储中FILE结构数组中的条目。

所以我的想法是:

  1. 使用fopen打开新文件。我们现在有一个新的FILE *到内部结构数组。
  2. 将此新指针保存到堆栈中。
  3. 现在只需将两个结构stdout与新的FILE结构交换。
  4. 代码应该是:

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

    执行此操作后,stdout句柄现在是新文件。旧的标准输出句柄在你保存在堆栈中的FILE *慢。

    仅返回:

    1. 从堆栈中获取文件+。
    2. 使用此FILE *再次交换stdout。 (交换完整的FILE结构)
    3. 关闭您收到的文件*。
    4. 如果要对文件句柄执行此操作,则需要将OS文件句柄与FILE *相关联。这是通过_open_osfhandle()和_fdopen()完成的。

      由于文件操作使用缓冲区,因此需要在交换之前刷新缓冲区。确保旧输出中没有“剩余物”。

      从我的角度来看,这个黑客应该有用。

答案 2 :(得分:0)

这是我放在一起的解决方案(当然远非完美)。它为写入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;
}