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

在提出任何替代scheme之前,让我准确地描述一下我正在做的事情(是的,我知道为此可以使用freopen )。 我需要做的是在修改之前将当前的标准输出句柄“保存”在一个类似于堆栈的数据结构上,以便能够恢复以前的输出句柄。 任何短小的情况都是不可接受的(即我无法恢复到CONOUT$等)。 这需要有recursion的能力。 也就是说,以下内容应该如你所期望的那样工作:

 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 。 我理想需要的是一个等同于在HANDLE上工作的freopen 。 这样我可以在GetStdHandle返回的HANDLE上使用DuplicateHandle ,然后使用MyReopenHandle函数将该HANDLE的底层文件设置为我喜欢的文件。 我相信,这将工作,因为我认为printfcout有一个HANDLE保存在内心深处。 我试图通过复制标准输出句柄来“伪造”,closures句柄,然后调用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; } 

上面的断言失败了大约一半的时间(我没有真正期待或指望那个工作…我只是感兴趣)。 就我所知,这个问题就是这样。 如果有人有任何build议,请让我知道:)

哇,经过一段时间寻找手动设置FILEHANDLE的方法,我终于发现有一个相当直接的方法来使用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为您调用SetStdHandle

这只适用于MS-CRT。

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

所以我的想法是:

  1. 用fopen打开新文件。 我们现在有一个新的文件*内部结构数组。
  2. 将这个新的指针保存到你的栈中。
  3. 现在只需将两个结构stdout与新的FILE结构交换。

代码应该是:

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

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

仅返回:

  1. 从堆栈中获取FILE +。
  2. 用这个FILE *再次交换stdout。 (交换完整的FILE结构)
  3. 关闭您收到的文件*。

如果要使用文件句柄来完成此操作,则需要将操作系统文件句柄与FILE *相关联。 这是用_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; }