标题说明了一切。当我运行以下代码时:
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!
也会被写入文件{{ 1}}是对Foo.txt
的调用的结果。我的假设是,当一切都在一开始就被初始化时,WriteFile
返回的HANDLE
被缓存并重新用于GetStdHandle
和cout
。这是完全合理的,正是我想要的,因为我认为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
的基础文件设置为我的文件喜好。我相信这会有效,因为我认为HANDLE
和printf
都有cout
保存在内心深处。我试图通过复制标准输出句柄,关闭该句柄,然后调用HANDLE
来试图“伪造它”,希望它能给我相同的CreateFile
值,但最多只能偶尔使用。如果您有兴趣,这是我的代码:
HANDLE
上述断言失败了大约一半的时间(我并没有真正期待或指望它起作用......我只是感兴趣)。就这个问题而言,这就是我所知道的。如果有人有任何建议,请告诉我:))
答案 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结构数组中的条目。
所以我的想法是:
代码应该是:
FILE swap = *stdout;
*stdout = *pFile;
*pFile = swap;
执行此操作后,stdout句柄现在是新文件。旧的标准输出句柄在你保存在堆栈中的FILE *慢。
仅返回:
如果要对文件句柄执行此操作,则需要将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;
}