[英]Win32 GUI C(++) app redirect both stdout and stderr to the same file on disk
我正在創建一個不能有關聯控制台的 Windows 服務。 因此,我想將 stdout 和 stderr 重定向到(相同的)文件。 這是我迄今為止發現的:
cout
和cerr
可以通過更改緩沖區來完成,但這不會像puts
或 Windows I/O 句柄那樣影響 CI/O。freopen
將 stdout 或 stderr 作為文件重新打開,但我們不能兩次指定同一個文件。dup2
將 stderr 重定向到 stdout,就像這里一樣。 到目前為止一切順利,當我們使用/SUBSYSTEM:CONSOLE
(項目屬性 → 鏈接器 → 系統)運行此代碼時,一切正常:
#include <Windows.h>
#include <io.h>
#include <fcntl.h>
#include <cstdio>
#include <iostream>
void doit()
{
FILE *stream;
if (_wfreopen_s(&stream, L"log.log", L"w", stdout)) __debugbreak();
// Also works as service when uncommenting this line: if (_wfreopen_s(&stream, L"log2.log", L"w", stderr)) __debugbreak();
if (_dup2(_fileno(stdout), _fileno(stderr)))
{
const auto err /*EBADF if service; hover over in debugger*/ = errno;
__debugbreak();
}
// Seemingly can be left out for console applications
if (!SetStdHandle(STD_OUTPUT_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stdout))))) __debugbreak();
if (!SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stderr))))) __debugbreak();
if (_setmode(_fileno(stdout), _O_WTEXT) == -1) __debugbreak();
if (_setmode(_fileno(stderr), _O_WTEXT) == -1) __debugbreak();
std::wcout << L"1☺a" << std::endl;
std::wcerr << L"1☺b" << std::endl;
_putws(L"2☺a");
fflush(stdout);
fputws(L"2☺b\n", stderr);
fflush(stderr);
const std::wstring a3(L"3☺a\n"), b3(L"3☺b\n");
if (!WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), a3.c_str(), a3.size() * sizeof(wchar_t), nullptr, nullptr))
__debugbreak();
if (!WriteFile(GetStdHandle(STD_ERROR_HANDLE), b3.c_str(), b3.size() * sizeof(wchar_t), nullptr, nullptr))
__debugbreak();
}
int main() { doit(); }
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { return doit(), 0; }
這很好地將以下文本寫入log.log
:
1☺a
1☺b
2☺a
2☺b
3☺a
3☺b
(當然我們想要表情符號,所以我們需要某種 unicode。在這種情況下,我們使用寬字符,這意味着我們需要使用setmode
否則一切都會搞砸。您可能還需要將 cpp 文件保存為MSVC 可以理解,例如帶有簽名的 UTF-8。)
但是現在回到最初的問題:將其作為沒有控制台的服務來執行,或者,等效但更易於調試的 GUI 應用程序 ( /SUBSYSTEM:WINDOWS
)。 問題是在這種情況下dup2
失敗,因為fileno(stderr)
不是有效的文件描述符,因為應用程序最初沒有關聯的流。 如此處所述,在這種情況下, fileno(stderr) == -2
。
請注意,當我們第一次使用freopen
打開 stderr 作為另一個文件時,一切正常,但我們創建了一個虛擬的空文件。
所以現在我的問題是:將 stdout 和 stderr 重定向到最初沒有流的應用程序中的同一文件的最佳方法是什么?
回顧一下:問題是當stdout
或stderr
沒有與輸出流關聯時, fileno
返回 -2 ,所以我們不能將它傳遞給dup2
。
(我不想更改用於實際打印的代碼,因為這可能意味着外部函數產生的某些輸出不會被重定向。)
這是一個程序示例,該程序創建一個用於寫入的文件,然后使用CreateProcess
並將該進程的stdout
和stderr
設置為所創建文件的HANDLE
。 這個例子只是從一個虛擬參數開始,讓它向stdout
和stderr
寫入很多東西,這些東西將被寫入output.txt
。
// RedirectStd.cpp
#include <iostream>
#include <string_view>
#include <vector>
#include <Windows.h>
struct SecAttrs_t : public SECURITY_ATTRIBUTES {
SecAttrs_t() : SECURITY_ATTRIBUTES{ 0 } {
nLength = sizeof(SECURITY_ATTRIBUTES);
bInheritHandle = TRUE;
}
operator SECURITY_ATTRIBUTES* () { return this; }
};
struct StartupInfo_t : public STARTUPINFO {
StartupInfo_t(HANDLE output) : STARTUPINFO{ 0 } {
cb = sizeof(STARTUPINFO);
dwFlags = STARTF_USESTDHANDLES;
hStdOutput = output;
hStdError = output;
}
operator STARTUPINFO* () { return this; }
};
int cppmain(const std::string_view program, std::vector<std::string_view> args) {
if (args.size() == 0) {
// no arguments, create a file and start a new process
SecAttrs_t sa;
HANDLE hFile = CreateFile(L"output.txt",
GENERIC_WRITE,
FILE_SHARE_READ,
sa, // lpSecurityAttributes
CREATE_ALWAYS, // dwCreationDisposition
FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
NULL // dwFlagsAndAttributesparameter
);
if (hFile == INVALID_HANDLE_VALUE) return 1;
StartupInfo_t su(hFile); // set output handles to hFile
PROCESS_INFORMATION pi;
std::wstring commandline = L"RedirectStd.exe dummy";
BOOL bCreated = CreateProcess(
NULL,
commandline.data(),
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
TRUE, // bInheritHandles
0, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
su, // lpStartupInfo
&pi
);
if (bCreated == 0) return 2;
CloseHandle(pi.hThread); // no need for this
WaitForSingleObject(pi.hProcess, INFINITE); // wait for the process to finish
CloseHandle(pi.hProcess);
CloseHandle(hFile);
}
else {
// called with an argument, output stuff to stdout and stderr
for (int i = 0; i < 1024; ++i) {
std::cout << "stdout\n";
std::cerr << "stderr\n";
}
}
return 0;
}
int main(int argc, char* argv[]) {
return cppmain(argv[0], { argv + 1, argv + argc });
}
我找到了一個有效的解決方案,並且不會創建臨時文件log2.log
。 我們可以打開NUL
(Windows 的/dev/null
)而不是這個文件,因此代碼變為:
FILE *stream;
if (_wfreopen_s(&stream, L"log.log", L"w", stdout)) __debugbreak();
if (freopen_s(&stream, "NUL", "w", stderr)) __debugbreak();
if (_dup2(_fileno(stdout), _fileno(stderr))) __debugbreak();
if (!SetStdHandle(STD_OUTPUT_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stdout))))) __debugbreak();
if (!SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stderr))))) __debugbreak();
if (_setmode(_fileno(stdout), _O_WTEXT) == -1) __debugbreak();
if (_setmode(_fileno(stderr), _O_WTEXT) == -1) __debugbreak();
這確保_fileno(stderr)
不再是-2
,所以我們可以使用dup2
。
可能有一個更優雅的解決方案(不確定),但這有效並且不會創建一個虛擬的空文件(也不是一個名為NUL
的文件)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.