WINMAIN y main() en C++(Extended)
windows (4)
Bien, he visto esta publicación: Diferencia entre WinMain, main y DllMain en C ++
Ahora sé que WINMAIN
se usa para aplicaciones de ventana y main()
para consolas. Pero leer la publicación realmente no me dice por qué exactamente cuál es la diferencia.
Quiero decir, ¿de qué sirve separar las diferentes funciones de red para comenzar un programa? ¿Se debe a problemas de rendimiento? O qué es?
Acerca de las funciones.
Los estándares C y C ++ requieren que cualquier programa (para una implementación "alojada" de C o C ++) tenga una función llamada main
, que sirve como función de inicio del programa. La función main
se llama después de la inicialización cero de variables estáticas no locales, y posiblemente, pero no necesariamente (!, C ++ 11 §3.6.2 / 4) esta llamada ocurre después de la inicialización dinámica de tales variables. Puede tener una de las siguientes firmas:
int main()
int main( int argc, char* argv[] )
más posibles firmas definidas por la implementación (C ++ 11 §3.6.1 / 2), excepto que el tipo de resultado debe ser int
.
Como la única función de este tipo en C ++ main
tiene un valor de resultado predeterminado , es decir, 0. Si main
regresa, después de la función ordinaria return se llama a exit
con el valor de resultado main
como argumento. El estándar define tres valores que se pueden usar garantizados: 0 (indica éxito), EXIT_SUCCESS
(también indica éxito, y típicamente se define como 0), y EXIT_FAILURE
(indica falla), donde las dos constantes con nombre están definidas por el <stdlib.h>
encabezado que también declara la función de exit
.
Los argumentos main
están destinados a representar los argumentos de línea de comando para el comando utilizado para iniciar el proceso. argc
(conteo de argumentos) es el número de elementos en la matriz argv
(valores de argumento). Además de esos elementos, argv[argc]
está garantizado que es 0. Si argc
> 0 - ¡que no está garantizado! - entonces se garantiza que argv[0]
sea un puntero a una cadena vacía, o un puntero al "nombre usado para invocar el programa". Este nombre puede incluir una ruta, y puede ser el nombre del ejecutable.
Usar los argumentos main
para obtener los argumentos de la línea de comandos funciona bien en * nix, porque C y C ++ se originaron con * nix. Sin embargo, el estándar de facto de Windows para la codificación de los argumentos main
es Windows ANSI , que no admite nombres de archivo generales de Windows (como, por ejemplo, para una instalación de Windows noruego, nombres de archivo con caracteres griegos o cirílicos). Por lo tanto, Microsoft optó por extender los lenguajes C y C ++ con una función de inicio específica de Windows llamada wmain
, que tiene argumentos basados en caracteres anchos codificados como UTF-16 , que pueden representar cualquier nombre de archivo.
La función wmain
puede tener una de estas firmas , correspondiente a las firmas estándar para main
:
int wmain()
int wmain( int argc, wchar_t* argv[] )
además de algunos más que no son especialmente útiles.
Es decir, wmain
es un reemplazo basado en caracteres directos para main
.
La función basada en caracteres WinMain
se introdujo con Windows a principios de los 80:
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
donde CALLBACK
, HINSTANCE
y LPSTR
están definidos por el encabezado <windows.h>
( LPSTR
es solo char*
).
Argumentos:
el valor del argumento
hInstance
es la dirección base de la imagen de memoria del ejecutable, se usa principalmente para cargar recursos desde el ejecutable, y alternativamente puede obtenerse de laGetModuleHandle
API.el argumento
hPrevInstance
siempre es 0,el argumento
lpCmdLine
se puede obtener alternativamente de la funciónGetCommandLine
API, más un poco de lógica extraña para omitir el nombre del programa como parte de la línea de comando, yel valor del argumento
nCmdShow
puede obtenerse alternativamente desde la funciónGetStartupInfo
API, pero con Windows moderno, la primera creación de una ventana de nivel superior hace eso automáticamente, por lo que no tiene ningún uso práctico.
Por lo tanto, la función WinMain
tiene los mismos inconvenientes que el estándar main
, más algunos (en particular, la verbosidad y el no estándar), y no tiene sus propias ventajas, por lo que es realmente inexplicable, excepto posiblemente como un bloqueo de proveedor. Sin embargo, con la cadena de herramientas de Microsoft hace que el enlazador se convierta por defecto en el subsistema GUI, que algunos ven como una ventaja. Pero con, por ejemplo, la cadena de herramientas GNU no tiene ese efecto, por lo que no se puede confiar en este efecto.
La función basada en wWinMain
wchar_t
es una variante de caracteres anchos de WinMain
, del mismo modo que wmain
es una variante de caracteres anchos de main
estándar:
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PWSTR lpCmdLine,
int nCmdShow
);
donde WINAPI
es lo mismo que CALLBACK
, y PWSTR
es simplemente wchar_t*
.
No hay una buena razón para usar cualquiera de las funciones no estándar excepto la menos conocida y la menos soportada, es wmain
, wmain
, y luego solo por conveniencia: esto evita usar las funciones de la API GetCommandLine
y CommandLineToArgvW
para recoger el GetCommandLine
UTF-16 codificado argumentos.
Para evitar que el enlazador de Microsoft se active (el enlazador de la cadena de herramientas de GNU no lo hace), simplemente establezca la variable de entorno LINK
en /entry:mainCRTStartup
, o especifique esa opción directamente. Esta es la función del punto de entrada de la biblioteca de tiempo de ejecución de Microsoft que, después de algunas inicializaciones, llama a la función main
estándar. Las otras funciones de inicio tienen funciones de punto de entrada correspondientes nombradas de la misma manera sistemática.
Ejemplos de uso de la función main
estándar.
Código fuente común:
foo.cpp
#undef UNICODE
#define UNICODE
#include <windows.h>
int main()
{
MessageBox( 0, L"Press OK", L"Hi", MB_SETFOREGROUND );
}
En los ejemplos a continuación (primero con la cadena de herramientas de GNU y luego con la cadena de herramientas de Microsoft), este programa se construye primero como un programa de subsistema de consola , y luego como un programa de subsistema GUI . Un programa de subsistema de consola, o en resumen solo un programa de consola , es aquel que requiere una ventana de consola. Este es el subsistema por defecto para todos los enlazadores de Windows que he usado (ciertamente no muchos), posiblemente para todos los enlazadores de Windows.
Para un programa de consola, Windows crea una ventana de consola automáticamente si es necesario. Cualquier proceso de Windows, independientemente del subsistema, puede tener una ventana de consola asociada y, como máximo, una. Además, el intérprete de comandos de Windows espera a que finalice un programa de programa de consola, de modo que la presentación de texto del programa haya finalizado.
Por el contrario, un programa de subsistema GUI es aquel que no requiere una ventana de consola. El intérprete de comandos no espera un programa del subsistema GUI, excepto en los archivos por lotes. Una forma de evitar la espera de finalización, para ambos tipos de programa, es usar el comando de start
. Una forma de presentar el texto de la ventana de consola desde un programa del subsistema GUI es redirigir su flujo de salida estándar. Otra forma es crear explícitamente una ventana de consola desde el código del programa.
El subsistema del programa está codificado en el encabezado del ejecutable. No se muestra en el Explorador de Windows (excepto que en Windows 9x se podía "ver rápidamente" un ejecutable, que presentaba casi la misma información que la herramienta de dumpbin
de Microsoft ahora). No hay un concepto correspondiente de C ++.
main
con la cadena de herramientas GNU.
[D:/dev/test] > g++ foo.cpp [D:/dev/test] > objdump -x a.exe | find /i "subsys" MajorSubsystemVersion 4 MinorSubsystemVersion 0 Subsystem 00000003 (Windows CUI) [544](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000004 __major_subsystem_version__ [612](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000003 __subsystem__ [636](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __minor_subsystem_version__ [D:/dev/test] > g++ foo.cpp -mwindows [D:/dev/test] > objdump -x a.exe | find /i "subsys" MajorSubsystemVersion 4 MinorSubsystemVersion 0 Subsystem 00000002 (Windows GUI) [544](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000004 __major_subsystem_version__ [612](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000002 __subsystem__ [636](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __minor_subsystem_version__ [D:/dev/test] > _
main
con toolchain de Microsoft:
[D:/dev/test] > set LINK=/entry:mainCRTStartup [D:/dev/test] > cl foo.cpp user32.lib foo.cpp [D:/dev/test] > dumpbin /headers foo.exe | find /i "subsys" 6.00 subsystem version 3 subsystem (Windows CUI) [D:/dev/test] > cl foo.cpp /link user32.lib /subsystem:windows foo.cpp [D:/dev/test] > dumpbin /headers foo.exe | find /i "subsys" 6.00 subsystem version 2 subsystem (Windows GUI) [D:/dev/test] > _
Ejemplos de uso de la función wmain
de Microsoft.
El siguiente código principal es común tanto para la cadena de herramientas de GNU como para las demostraciones de Microsoft toolchain:
bar.cpp
#undef UNICODE
#define UNICODE
#include <windows.h>
#include <string> // std::wstring
#include <sstream> // std::wostringstream
using namespace std;
int wmain( int argc, wchar_t* argv[] )
{
wostringstream text;
text << argc - 1 << L" command line arguments:/n";
for( int i = 1; i < argc; ++i )
{
text << "/n[" << argv[i] << "]";
}
MessageBox( 0, text.str().c_str(), argv[0], MB_SETFOREGROUND );
}
wmain
con la cadena de herramientas GNU.
El toolchain de GNU no es compatible con la función wmain
de Microsoft:
[D:/dev/test] > g++ bar.cpp d:/bin/mingw/bin/../lib/gcc/i686-pc-mingw32/4.7.1/../../../libmingw32.a(main.o):main.c:(.text.startup+0xa3): undefined reference to `WinMain @16'' collect2.exe: error: ld returned 1 exit status [D:/dev/test] > _
El mensaje de error del enlace aquí, sobre WinMain
, se debe a que el toolchain de GNU admite esa función (presumiblemente porque el código antiguo lo usa), y lo busca como último recurso después de no poder encontrar un main
estándar.
Sin embargo, es trivial agregar un módulo con un main
estándar que llame al wmain
:
wmain_support.cpp
extern int wmain( int, wchar_t** );
#undef UNICODE
#define UNICODE
#include <windows.h> // GetCommandLine, CommandLineToArgvW, LocalFree
#include <stdlib.h> // EXIT_FAILURE
int main()
{
struct Args
{
int n;
wchar_t** p;
~Args() { if( p != 0 ) { ::LocalFree( p ); } }
Args(): p( ::CommandLineToArgvW( ::GetCommandLine(), &n ) ) {}
};
Args args;
if( args.p == 0 )
{
return EXIT_FAILURE;
}
return wmain( args.n, args.p );
}
Ahora,
[D:/dev/test] > g++ bar.cpp wmain_support.cpp [D:/dev/test] > objdump -x a.exe | find /i "subsystem" MajorSubsystemVersion 4 MinorSubsystemVersion 0 Subsystem 00000003 (Windows CUI) [13134](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000004 __major_subsystem_version__ [13576](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000003 __subsystem__ [13689](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __minor_subsystem_version__ [D:/dev/test] > g++ bar.cpp wmain_support.cpp -mwindows [D:/dev/test] > objdump -x a.exe | find /i "subsystem" MajorSubsystemVersion 4 MinorSubsystemVersion 0 Subsystem 00000002 (Windows GUI) [13134](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000004 __major_subsystem_version__ [13576](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000002 __subsystem__ [13689](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 __minor_subsystem_version__ [D:/dev/test] > _
wmain
con la cadena de herramientas de Microsoft.
Con la cadena de herramientas de Microsoft, el enlazador infiere automáticamente el punto de entrada wmainCRTStartup
si no se especifica ningún punto de entrada y está presente una función wmain
(no está claro qué sucede si también hay un main
estándar, no lo he comprobado en los últimos años):
[D:/dev/test] > set link=/entry:mainCRTStartup [D:/dev/test] > cl bar.cpp user32.lib bar.cpp LIBCMT.lib(crt0.obj) : error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup bar.exe : fatal error LNK1120: 1 unresolved externals [D:/dev/test] > set link= [D:/dev/test] > cl bar.cpp user32.lib bar.cpp [D:/dev/test] > _
Sin embargo, con una función de arranque no estándar como wmain
, probablemente sea mejor especificar el punto de entrada explícitamente, para que quede muy claro sobre la intención:
[D:/dev/test] > cl bar.cpp /link user32.lib /entry:wmainCRTStartup bar.cpp [D:/dev/test] > dumpbin /headers bar.exe | find /i "subsystem" 6.00 subsystem version 3 subsystem (Windows CUI) [D:/dev/test] > cl bar.cpp /link user32.lib /entry:wmainCRTStartup /subsystem:windows bar.cpp [D:/dev/test] > dumpbin /headers bar.exe | find /i "subsystem" 6.00 subsystem version 2 subsystem (Windows GUI) [D:/dev/test] > _
De acuerdo con @RaymondChen
El nombre WinMain es solo una convención
Aunque la función WinMain está documentada en Platform SDK, no es realmente parte de la plataforma. Por el contrario, WinMain es el nombre convencional para el punto de entrada proporcionado por el usuario a un programa de Windows.
El punto de entrada real está en la biblioteca C runtime, que inicializa el tiempo de ejecución, ejecuta constructores globales y luego llama a su función WinMain (o wWinMain si prefiere un punto de entrada Unicode).
DllMain y WinMain son diferentes en sus propios prototipos. WinMain acepta el argumento de la línea de comando mientras que el otro habla de cómo está unido al proceso.
Según la documentación de MSDN
De forma predeterminada, la dirección de inicio es un nombre de función de la biblioteca de tiempo de ejecución de C. El vinculador lo selecciona de acuerdo con los atributos del programa, como se muestra en la siguiente tabla.
mainCRTStartup
(owmainCRTStartup
) Una aplicación que usa/SUBSYSTEM:CONSOLE;
llamadas main (owmain
)WinMainCRTStartup
(owWinMainCRTStartup
) Una aplicación que usa/SUBSYSTEM:WINDOWS;
llama aWinMain
(owWinMain
), que debe definirse con__stdcall
_DllMainCRTStartup
Una DLL; llama aDllMain
, que debe definirse con__stdcall
, si existe
Recuerdo vagamente haber leído en alguna parte que los programas de Windows tienen una función main()
. Simplemente está escondido en un encabezado o biblioteca en alguna parte. Creo que esta función main()
inicializa todas las variables necesarias para WinMain()
y luego las llama.
Por supuesto, soy un novato de WinAPI, por lo que espero que otros que sean más conocedores me corrijan si estoy equivocado.
Un programa C estándar pasa 2 parámetros por la línea de comando al inicio:
int main( int argc, char** argv ) ;
-
char** argv
es una matriz de cadenas (char*
) -
int argc
es el número dechar*
en argv
La función de arranque WinMain
que los programadores tienen que escribir para un programa de Windows es ligeramente diferente. WinMain
toma 4 parámetros que Win O / S pasa al programa al inicio:
int WINAPI WinMain( HINSTANCE hInstance, // HANDLE TO AN INSTANCE. This is the "handle" to YOUR PROGRAM ITSELF.
HINSTANCE hPrevInstance,// USELESS on modern windows (totally ignore hPrevInstance)
LPSTR szCmdLine, // Command line arguments. similar to argv in standard C programs
int iCmdShow ) // Start window maximized, minimized, etc.
Ver mi artículo Cómo crear una ventana básica en C para más