WINMAIN y main () en C ++ (Extended)

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 progtwig? ¿Se debe a problemas de rendimiento? O qué es?

    Acerca de las funciones.

    Los estándares C y C ++ requieren que cualquier progtwig (para una implementación “alojada” de C o C ++) tenga una función llamada main , que sirve como función de inicio del progtwig. 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 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 progtwig”. 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 encoding 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 ( 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 la GetModuleHandle API.

    • el argumento hPrevInstance siempre es 0,

    • el argumento lpCmdLine se puede obtener alternativamente de la función GetCommandLine API, más un poco de lógica extraña para omitir el nombre del progtwig como parte de la línea de comando, y

    • el valor del argumento nCmdShow puede obtenerse alternativamente desde la función GetStartupInfo 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 locking 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  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 progtwig se construye primero como un progtwig de subsistema de consola , y luego como un progtwig de subsistema GUI . Un progtwig de subsistema de consola, o en resumen solo un progtwig 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 progtwig 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 progtwig de progtwig de consola, de modo que la presentación de texto del progtwig haya finalizado.

    Por el contrario, un progtwig de subsistema GUI es aquel que no requiere una ventana de consola. El intérprete de comandos no espera un progtwig del subsistema GUI, excepto en los archivos por lotes. Una forma de evitar la espera de finalización, para ambos tipos de progtwig, es usar el comando de start . Una forma de presentar el texto de la ventana de consola desde un progtwig 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 progtwig.

    El subsistema del progtwig 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 \ prueba]
     > g ++ foo.cpp
    
     [D: \ dev \ prueba]
     > objdump -x a.exe |  encontrar / i "subsiste"
     MajorSubsystemVersion 4
     MinorSubsystemVersion 0
     Subsistema 00000003 (Windows CUI)
     [544] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000004 __major_subsystem_version__
     [612] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000003 __subsystem__
     [636] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__
    
     [D: \ dev \ prueba]
     > g ++ foo.cpp -mwindows
    
     [D: \ dev \ prueba]
     > objdump -x a.exe |  encontrar / i "subsiste"
     MajorSubsystemVersion 4
     MinorSubsystemVersion 0
     Subsistema 00000002 (GUI de Windows)
     [544] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000004 __major_subsystem_version__
     [612] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000002 __subsystem__
     [636] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__
    
     [D: \ dev \ prueba]
     > _
    

    main con toolchain de Microsoft:

     [D: \ dev \ prueba]
     > establecer LINK = / entry: mainCRTStartup
    
     [D: \ dev \ prueba]
     > cl foo.cpp user32.lib
     foo.cpp
    
     [D: \ dev \ prueba]
     > dumpbin / headers foo.exe |  encontrar / i "subsiste"
                 6.00 versión del subsistema
                    3 subsistema (Windows CUI)
    
     [D: \ dev \ prueba]
     > cl foo.cpp / link user32.lib / subsystem: windows
     foo.cpp
    
     [D: \ dev \ prueba]
     > dumpbin / headers foo.exe |  encontrar / i "subsiste"
                 6.00 versión del subsistema
                    2 subsistema (GUI de Windows)
    
     [D: \ dev \ prueba]
     > _
    

    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  #include  // std::wstring #include  // 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 \ prueba]
     > g ++ bar.cpp
     d: / bin / mingw / bin /../ lib / gcc / i686-pc-mingw32 / 4.7.1 /../../../ libmingw32.a (main.o): main.c :(. text.startup + 0xa3): referencia indefinida a `WinMain
     @dieciséis'
     collect2.exe: error: ld devolvió 1 estado de salida
    
     [D: \ dev \ prueba]
     > _
    

    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  // GetCommandLine, CommandLineToArgvW, LocalFree #include  // 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 \ prueba]
     > g ++ bar.cpp wmain_support.cpp
    
     [D: \ dev \ prueba]
     > objdump -x a.exe |  encontrar / i "subsistema"
     MajorSubsystemVersion 4
     MinorSubsystemVersion 0
     Subsistema 00000003 (Windows CUI)
     [13134] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000004 __major_subsystem_version__
     [13576] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000003 __subsystem__
     [13689] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__
    
     [D: \ dev \ prueba]
     > g ++ bar.cpp wmain_support.cpp -mwindows
    
     [D: \ dev \ prueba]
     > objdump -x a.exe |  encontrar / i "subsistema"
     MajorSubsystemVersion 4
     MinorSubsystemVersion 0
     Subsistema 00000002 (GUI de Windows)
     [13134] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000004 __major_subsystem_version__
     [13576] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000002 __subsystem__
     [13689] (seg -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version__
    
     [D: \ dev \ prueba]
     > _
    

    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 \ prueba]
     > set link = / entry: mainCRTStartup
    
     [D: \ dev \ prueba]
     > cl bar.cpp user32.lib
     bar.cpp
     LIBCMT.lib (crt0.obj): error LNK2019: símbolo externo sin resolver _se hace referencia en la función ___tmainCRTStartup
     bar.exe: error fatal LNK1120: 1 external externo sin resolver
    
     [D: \ dev \ prueba]
     > establecer enlace =
    
     [D: \ dev \ prueba]
     > cl bar.cpp user32.lib
     bar.cpp
    
     [D: \ dev \ prueba]
     > _
    

    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 \ prueba]
     > cl bar.cpp / link user32.lib / entry: wmainCRTStartup
     bar.cpp
    
     [D: \ dev \ prueba]
     > dumpbin / headers bar.exe |  encontrar / i "subsistema"
                 6.00 versión del subsistema
                    3 subsistema (Windows CUI)
    
     [D: \ dev \ prueba]
     > cl bar.cpp / link user32.lib / entry: wmainCRTStartup / subsystem: windows
     bar.cpp
    
     [D: \ dev \ prueba]
     > dumpbin / headers bar.exe |  encontrar / i "subsistema"
                 6.00 versión del subsistema
                    2 subsistema (GUI de Windows)
    
     [D: \ dev \ prueba]
     > _
    

    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 progtwig 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 progtwig, como se muestra en la siguiente tabla.

    • mainCRTStartup (o wmainCRTStartup ) Una aplicación que usa /SUBSYSTEM:CONSOLE; llamadas main (o wmain )

    • WinMainCRTStartup (o wWinMainCRTStartup ) Una aplicación que usa /SUBSYSTEM:WINDOWS; llama a WinMain (o wWinMain ), que debe definirse con __stdcall

    • _DllMainCRTStartup Una DLL; llama a DllMain , que debe definirse con __stdcall , si existe

    Un progtwig 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 de char* en argv

    La función de arranque WinMain que los progtwigdores tienen que escribir para un progtwig de Windows es ligeramente diferente. WinMain toma 4 parámetros que Win O / S pasa al progtwig 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

    Recuerdo vagamente haber leído en alguna parte que los progtwigs 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.