我有一个GUI应用程序,我正在开发Linux和Windows的跨平台应用程序。在Linux上,一切都很顺利。然而,我在Windows上遇到了一个问题。我希望能够在Windows上使用GUI应用程序记录某些消息到控制台,linux风格。
我所说的linux风格的意思是,如果从控制台打开程序,输出将进入控制台,但是如果程序被打开,例如,通过开始菜单,用户将永远不会看到控制台输出。显然,这比在Windows上听起来要难。
目前,我在main()中使用以下技巧:
#if _WINDOWS /* Fix console output on Windows */
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$","wb",stdout);
freopen("CONOUT$","wb",stderr);
}
#endif
这允许我在程序实际打开窗口之前创建输出,例如从命令行响应"——help"。但是,一旦我的程序实际初始化并打开了一个窗口,就会返回控制台。我需要一种解决方案,允许我在程序的整个生命周期中继续访问控制台,如果最初没有使用控制台,则无需打开新控制台。
我们使用::AllocConsole()而不是::AttachConsole,它在整个应用程序中保持打开状态。
目前为止我发现的最好的解决方案是有两个可执行文件。
-
program.exe
是GUI应用程序。 -
program.com
是一个辅助命令行应用程序,它生成program.exe
并向其传递标准I/O。(它不是来自DOS的COM可执行文件,它只是一个重命名的标准PE可执行文件;由于.com
在cmd.exe
的默认优先顺序中位于.exe
之前,因此您可以键入program
,如果两者都在路径中,它将自动调用program.com
而不是program.exe
。
有了这个设置,你可以在Windows命令提示符下输入program
,写入program.exe
的标准输出,它将正确地出现在控制台上;当你从GUI打开program.exe
时,不会产生控制台窗口。
下面是一个帮助程序的示例实现,取自Inkscape:http://bazaar.launchpad.net/~ inkscape.dev/inkscape/箱子/视图/负责人:/src/winconsole.cpp
帮助器创建三个管道,并使用CreateProcess
生成GUI程序,为其提供管道的适当末端。然后,它创建三个线程,在无限循环中在管道和辅助程序的标准I/O之间复制数据。帮助器被编译为控制台应用程序(重要)- MinGW中的-mconsole
开关。
/**
* file
* Command-line wrapper for Windows.
*
* Windows has two types of executables: GUI and console.
* The GUI executables detach immediately when run from the command
* prompt (cmd.exe), and whatever you write to standard output
* disappears into a black hole. Console executables
* do display standard output and take standard input from the console,
* but when you run them from the GUI, an extra console window appears.
* It's possible to hide it, but it still flashes for a fraction
* of a second.
*
* To provide an Unix-like experience, where the application will behave
* correctly in command line mode and at the same time won't create
* the ugly console window when run from the GUI, we have to have two
* executables. The first one, inkscape.exe, is the GUI application.
* Its entry points are in main.cpp and winmain.cpp. The second one,
* called inkscape.com, is a small helper application contained in
* this file. It spawns the GUI application and redirects its output
* to the console.
*
* Note that inkscape.com has nothing to do with "compact executables"
* from DOS. It's a normal PE executable renamed to .com. The trick
* is that cmd.exe picks .com over .exe when both are present in PATH,
* so when you type "inkscape" into the command prompt, inkscape.com
* gets run. The Windows program loader does not inspect the extension,
* just like an Unix program loader; it determines the binary format
* based on the contents of the file.
*
*//*
* Authors:
* Jos Hirth <jh@kaioa.com>
* Krzysztof Kosinski <tweenk.pl@gmail.com>
*
* Copyright (C) 2008-2010 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef WIN32
#undef DATADIR
#include <windows.h>
struct echo_thread_info {
HANDLE echo_read;
HANDLE echo_write;
unsigned buffer_size;
};
// thread function for echoing from one file handle to another
DWORD WINAPI echo_thread(void *info_void)
{
echo_thread_info *info = static_cast<echo_thread_info*>(info_void);
char *buffer = reinterpret_cast<char *>(LocalAlloc(LMEM_FIXED, info->buffer_size));
DWORD bytes_read, bytes_written;
while(true){
if (!ReadFile(info->echo_read, buffer, info->buffer_size, &bytes_read, NULL) || bytes_read == 0)
if (GetLastError() == ERROR_BROKEN_PIPE)
break;
if (!WriteFile(info->echo_write, buffer, bytes_read, &bytes_written, NULL)) {
if (GetLastError() == ERROR_NO_DATA)
break;
}
}
LocalFree(reinterpret_cast<HLOCAL>(buffer));
CloseHandle(info->echo_read);
CloseHandle(info->echo_write);
return 1;
}
int main()
{
// structs that will store information for our I/O threads
echo_thread_info stdin = {NULL, NULL, 4096};
echo_thread_info stdout = {NULL, NULL, 4096};
echo_thread_info stderr = {NULL, NULL, 4096};
// handles we'll pass to inkscape.exe
HANDLE inkscape_stdin, inkscape_stdout, inkscape_stderr;
HANDLE stdin_thread, stdout_thread, stderr_thread;
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;
// Determine the path to the Inkscape executable.
// Do this by looking up the name of this one and redacting the extension to ".exe"
const int pathbuf = 2048;
WCHAR *inkscape = reinterpret_cast<WCHAR*>(LocalAlloc(LMEM_FIXED, pathbuf * sizeof(WCHAR)));
GetModuleFileNameW(NULL, inkscape, pathbuf);
WCHAR *dot_index = wcsrchr(inkscape, L'.');
wcsncpy(dot_index, L".exe", 4);
// we simply reuse our own command line for inkscape.exe
// it guarantees perfect behavior w.r.t. quoting
WCHAR *cmd = GetCommandLineW();
// set up the pipes and handles
stdin.echo_read = GetStdHandle(STD_INPUT_HANDLE);
stdout.echo_write = GetStdHandle(STD_OUTPUT_HANDLE);
stderr.echo_write = GetStdHandle(STD_ERROR_HANDLE);
CreatePipe(&inkscape_stdin, &stdin.echo_write, &sa, 0);
CreatePipe(&stdout.echo_read, &inkscape_stdout, &sa, 0);
CreatePipe(&stderr.echo_read, &inkscape_stderr, &sa, 0);
// fill in standard IO handles to be used by the process
PROCESS_INFORMATION pi;
STARTUPINFOW si;
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = inkscape_stdin;
si.hStdOutput = inkscape_stdout;
si.hStdError = inkscape_stderr;
// spawn inkscape.exe
CreateProcessW(inkscape, // path to inkscape.exe
cmd, // command line as a single string
NULL, // process security attributes - unused
NULL, // thread security attributes - unused
TRUE, // inherit handles
0, // flags
NULL, // environment - NULL = inherit from us
NULL, // working directory - NULL = inherit ours
&si, // startup info - see above
&pi); // information about the created process - unused
// clean up a bit
LocalFree(reinterpret_cast<HLOCAL>(inkscape));
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(inkscape_stdin);
CloseHandle(inkscape_stdout);
CloseHandle(inkscape_stderr);
// create IO echo threads
DWORD unused;
stdin_thread = CreateThread(NULL, 0, echo_thread, (void*) &stdin, 0, &unused);
stdout_thread = CreateThread(NULL, 0, echo_thread, (void*) &stdout, 0, &unused);
stderr_thread = CreateThread(NULL, 0, echo_thread, (void*) &stderr, 0, &unused);
// wait until the standard output thread terminates
WaitForSingleObject(stdout_thread, INFINITE);
return 0;
}
#endif
我记得读过一些关于这个的东西,如果我没记错的话,解决方案是在控制台项目中添加gui,而不是在gui项目中添加控制台,因为后者只能通过打开一个新控制台来完成。
我认为你应该创建一个控制台应用程序,然后检查谁初始化了进程(可能是cmd.exe),并取决于你可以隐藏控制台窗口。然后在其中创建一个窗口……这样做的缺点是控制台窗口可能会打开一会儿,直到你把它隐藏起来,它看起来会很难看,我想。稍后打开控制台不会有这个问题,但我不知道标准输出是否会像在控制台应用程序中那样重定向到它,或者你是否必须以某种方式设置它,或者你可能必须在每个调用中重定向…不,一定有更好的办法!