c++ - Windows GUI+Salida de consola, estilo Linux
user-interface console (4)
Tengo una aplicación GUI, que estoy desarrollando multiplataforma para Linux y Windows. En Linux, todo funciona sin problemas. Sin embargo, me he encontrado con un problema en Windows. Me gustaría poder registrar ciertos mensajes en la consola con una aplicación GUI en Windows, al estilo de Linux.
Lo que quiero decir con estilo Linux es que, si el programa se abre desde una consola, la salida irá a la consola, pero si el programa se abre, por ejemplo, a través del menú de inicio, el usuario nunca verá la salida de la consola. Al parecer, esto es más difícil de lo que parece en Windows.
Actualmente, uso los siguientes trucos en main ():
#if _WINDOWS /* Fix console output on Windows */
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$","wb",stdout);
freopen("CONOUT$","wb",stderr);
}
#endif
Esto me permite crear una salida antes de que el programa realmente abra una ventana, como responder a "--help" desde la línea de comandos. Sin embargo, una vez que la ventana es inicializada y abierta por mi programa, se devuelve la consola. Necesito una solución que me permita el acceso continuo a la consola a lo largo de la vida de mi programa, sin abrir una nueva consola si no se usó originalmente.
Creo que deberías crear una aplicación de consola y luego verificar quién inicializó el proceso (probablemente cmd.exe) y, según eso, puedes ocultar la ventana de la consola. Luego creas una ventana en ella ... lo importante es que la ventana de la consola puede estar abierta por un momento hasta que la ocultes y se verá muy fea, supongo. Al abrir más tarde, la consola no tiene ese problema, pero no sé si la salida estándar se redirige a ella como ocurre en las aplicaciones de la consola o si tiene que configurarlo de alguna manera, o quizás deba redirigirse en cada llamada ... no , ¡Tiene que haber una mejor manera!
La mejor solución que he encontrado hasta ahora es tener dos ejecutables.
-
program.exe
es la aplicación GUI. -
program.com
es una aplicación de línea de comandos auxiliar que generaprogram.exe
y le pasa I / O estándar. (No es un ejecutable COM de DOS, solo es un ejecutable PE estándar renombrado; dado que.com
es anterior a.exe
en el orden de preferencia predeterminado decmd.exe
, puede escribirprogram
y llamará automáticamente aprogram.com
lugar de aprogram.exe
si ambos están en el camino.)
Con esta configuración, puede escribir program
en el símbolo del sistema de Windows, escribir en la salida estándar en program.exe
, y aparecerá correctamente en la consola; no se generarán ventanas de consola cuando abra program.exe
desde la GUI.
Aquí hay un ejemplo de implementación del programa de ayuda, tomado de Inkscape: http://bazaar.launchpad.net/~inkscape.dev/inkscape/trunk/view/head:/src/winconsole.cpp
El ayudante crea tres canalizaciones y genera el programa GUI con CreateProcess
, dándole los extremos apropiados de las tuberías. Luego crea tres subprocesos que copian datos entre las tuberías y la E / S estándar del programa auxiliar en un bucle infinito. El asistente se compila como una aplicación de consola (importante): el conmutador -mconsole
en MinGW.
/**
* /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 <[email protected]>
* Krzysztof Kosinski <[email protected]>
*
* 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
Recuerdo haber leído algo sobre esto y si recuerdo correctamente, la solución fue agregar una interfaz gráfica de usuario a un proyecto de consola en lugar de agregar una consola a un proyecto de interfaz gráfica de usuario, ya que esta última solo se podía hacer abriendo una nueva consola.
Usamos :: AllocConsole () en lugar de :: AttachConsole y permanece abierto en toda la aplicación. ¿Trata eso?