web-dev-qa-db-fra.com

WINMAIN et main () en C++ (étendu)

Oui, j’ai regardé ce post: Différence entre WinMain, main et DllMain en C++

Je sais maintenant que WINMAIN est utilisé pour les applications Windows et main() pour les consoles. Mais lire le post ne me dit pas vraiment pourquoi exactement quelle est la différence.

Je veux dire, à quoi sert-il de séparer différentes fonctions principales pour démarrer un programme? Est-ce dû à des problèmes de performances? Ou c'est quoi?

45
Danny

A propos des fonctions.

Les normes C et C++ exigent que tout programme (pour une implémentation C ou C++ “hébergée”) ait une fonction appelée main, qui sert de fonction de démarrage du programme. La fonction main est appelée après initialisation à zéro de variables statiques non locales, et éventuellement mais pas nécessairement (!, C++ 11 §3.6.2/4) cet appel a lieu après initialisation dynamique de telles variables. Il peut avoir l'une des signatures suivantes:

int main()
int main( int argc, char* argv[] )

plus les signatures possibles définies par l'implémentation (C++ 11 §3.6.1/2), sauf que le type de résultat doit être int.

Comme la seule fonction de ce type dans C++, main a une valeur résultat par défaut, à savoir 0. Si main est renvoyé, la valeur retournée après la fonction ordinaire exit est appelée avec la valeur de résultat main comme argument. La norme définit trois valeurs qui peuvent être utilisées: 0 (indique le succès), EXIT_SUCCESS (indique également le succès et est généralement défini comme 0) et EXIT_FAILURE (indique l'échec ), où les deux constantes nommées sont définies par l'en-tête <stdlib.h> qui déclare également la fonction exit.

Les arguments main sont destinés à représenter les arguments de ligne de commande pour la commande utilisée pour démarrer le processus. argc (nombre d'arguments) est le nombre d'éléments dans le tableau argv (valeurs d'arguments). En plus de ces éléments, argv[argc] est garanti égal à 0. Si argc> 0 - ce qui n’est pas garanti! - alors il est garanti que argv[0] soit un pointeur sur une chaîne vide, ou un pointeur sur le "nom utilisé pour appeler le programme". Ce nom peut inclure un chemin et il peut s'agir du nom de l'exécutable.

L'utilisation des arguments main pour obtenir les arguments de ligne de commande fonctionne très bien dans * nix, car C et C++ sont issus de * nix. Cependant, le de facto Le standard Windows pour le codage des arguments main est Windows ANSI, qui ne prend pas en charge les noms de fichiers Windows généraux (tels que, pour une installation Windows norvégienne, les noms de fichiers avec des caractères grecs ou cyrilliques). C'est pourquoi Microsoft a choisi d'étendre les langages C et C++ avec une fonction de démarrage spécifique à Windows appelée wmain, qui possède des arguments basés sur des caractères larges et codés sous la forme TF-16, qui peuvent nom de fichier.

La fonction wmain peut avoir ne de ces signatures , correspondant aux signatures standard pour main:

int wmain()
int wmain( int argc, wchar_t* argv[] )

plus quelques autres qui ne sont pas particulièrement utiles.

C'est-à-dire, wmain est un remplacement direct basé sur des caractères larges pour main.

La fonction WinMainchar a été introduite avec Windows au début des années 1980:

int CALLBACK WinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    LPSTR       lpCmdLine,
    int         nCmdShow
    );

CALLBACK, HINSTANCE et LPSTR sont définis par l'en-tête <windows.h> (LPSTR est simplement char*).

Arguments:

  • la valeur de l'argument hInstance est l'adresse de base de l'image mémoire de l'exécutable. Elle est principalement utilisée pour charger des ressources à partir de l'exécutable. Vous pouvez également l'obtenir à partir de la fonction API GetModuleHandle.

  • l'argument hPrevInstance est toujours 0,

  • l'argument lpCmdLine peut également être obtenu à partir de la fonction API GetCommandLine, plus un peu de logique étrange pour ignorer la partie du nom de programme de la ligne de commande, et

  • la valeur de l'argument nCmdShow peut également être obtenue à partir de la fonction API GetStartupInfo, mais avec Windows moderne, la première création d'une fenêtre de niveau supérieur le fait automatiquement, de sorte qu'elle n'a aucune utilité pratique.

Ainsi, la fonction WinMain présente les mêmes inconvénients que la norme main, plus quelques-uns (en particulier la verbosité et le caractère non standard), et ne présente aucun avantage en elle-même. Elle est donc vraiment inexplicable, sauf peut-être en tant que fournisseur verrouillé. Toutefois, avec la chaîne d’outils Microsoft, l’éditeur de liens est défini par défaut sur le sous-système graphique, ce que certains considèrent comme un avantage. Mais avec par exemple Si la chaîne d’instruments GNU n’a pas cet effet, vous ne pouvez pas compter sur cet effet.

La fonction wWinMainwchar_t est une variante de caractère large de WinMain, de la même manière que wmain est une variante de caractère large de la norme main:

int WINAPI wWinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    PWSTR       lpCmdLine,
    int         nCmdShow
    );

WINAPI est identique à CALLBACK et PWSTR est tout simplement wchar_t*.

Il n'y a aucune raison d'utiliser les fonctions non standard, à l'exception des fonctions les moins connues et les moins prises en charge, à savoir wmain, puis simplement pour des raisons de commodité: vous évitez ainsi d'utiliser les fonctions de l'API GetCommandLine et CommandLineToArgvW pour capturer les données codées UTF-16. arguments.

Pour éviter que l'éditeur de liens Microsoft ne se matérialise (contrairement à l'éditeur de liens de la chaîne d'outils GNU, ne le faites pas), définissez simplement la variable d'environnement LINK sur /entry:mainCRTStartup ou spécifiez directement cette option. Il s'agit de la fonction de point d'entrée de la bibliothèque d'exécution Microsoft qui, après une initialisation, appelle la fonction standard main. Les autres fonctions de démarrage ont des fonctions de point d’entrée correspondantes nommées de la même manière systématique.


Exemples d'utilisation de la fonction standard main.

Code source commun:

foo.cpp

#undef UNICODE
#define UNICODE
#include <windows.h>

int main()
{
    MessageBox( 0, L"Press OK", L"Hi", MB_SETFOREGROUND );
}

Dans les exemples ci-dessous (d'abord avec la chaîne GNU, puis avec la chaîne Microsoft), ce programme est d'abord construit sous la forme d'un programme sous-système console, puis d'un programme sous-système à interface graphique. Un programme de sous-système de console, ou plus simplement un programme de console, requiert une fenêtre de console. C'est le sous-système par défaut pour tous les lieurs Windows que j'ai utilisés (certes pas beaucoup), probablement pour toute la période des lieurs Windows.

Pour un programme de console, Windows crée automatiquement --- si nécessaire une fenêtre de console. Tout processus Windows, quel que soit le sous-système, peut avoir une fenêtre de console associée et au plus une. En outre, l'interpréteur de commandes Windows attend la fin d'un programme de programme de la console afin que la présentation textuelle du programme soit terminée.

Inversement, un programme de sous-système graphique est un programme qui ne nécessite pas de fenêtre de console. L'interpréteur de commandes n'attend pas un programme de sous-système à interface graphique, sauf dans les fichiers de traitement par lots. Une façon d'éviter l'attente d'achèvement, pour les deux types de programme, consiste à utiliser la commande start. Une façon de présenter le texte de la fenêtre de la console à partir d'un programme de sous-système à interface graphique consiste à rediriger son flux de sortie standard. Une autre méthode consiste à créer explicitement une fenêtre de console à partir du code du programme.

Le sous-système du programme est codé dans l'en-tête de l'exécutable. Ce n’est pas indiqué par l’explorateur Windows (sauf que dans Windows 9x, il est possible de "visualiser rapidement" un fichier exécutable qui présente à peu près les mêmes informations que l’outil dumpbin de Microsoft le fait maintenant). Il n'y a pas de concept C++ correspondant.

main avec la chaîne GNU.

 [D:\dev\test] 
> g ++ foo.cpp
 
 [D:\dev\test] 
> objdump -x a.exe | find/i "subsys"
 MajorSubsystemVersion 4 
 MinorSubsystemVersion 0 
 Sous-système 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 __sous-système __ 
 [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 
 Sous-système 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 __sous-système __ 
 [636] (sec -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version __ 
 
 [D:\dev\test] 
> _ 

main avec la chaîne d’outils 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 Version du sous-système 
 3 sous-système (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"
 Version du sous-système 6.00 
 2 sous-système (interface graphique Windows) 
 
 [D:\dev\test] 
> _ 

Exemples d’utilisation de la fonction wmain de Microsoft.

Le code principal suivant est commun aux démonstrations de la chaîne d’outils GNU et de Microsoft:

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 avec la chaîne GNU.

La chaîne d'outils GNU ne prend pas en charge la fonction 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): référence non définie à `WinMain 
 @ 16 '
 collect2.exe: erreur: ld a renvoyé un état de sortie 
 
 [D:\dev\test] 
> _ 

Le message d’erreur de lien ici, à propos de WinMain, vient du fait que la chaîne d’outils GNU prend en charge cette fonction (probablement parce qu’un code aussi ancien l’utilise), et le recherche un dernier recours après avoir omis de trouver une norme main.

Cependant, il est trivial d’ajouter un module avec un main standard qui appelle le 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 );
}

À présent,

 [D:\dev\test] 
> g ++ bar.cpp wmain_support.cpp
 
 [D:\dev\test] 
> objdump -x a.exe | find/i "sous-système"
 MajorSubsystemVersion 4 
 MinorSubsystemVersion 0 
 Sous-système 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 "sous-système"
 MajorSubsystemVersion 4 
 MinorSubsystemVersion 0 
 Sous-système 00000002 (Interface graphique de Windows) 
 [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 avec la chaîne d’outils de Microsoft.

Avec la chaîne d’outils de Microsoft, l’éditeur de liens déduit automatiquement le point d’entrée wmainCRTStartup si aucun point d’entrée n’est spécifié et qu’une fonction wmain est présente (il est difficile de savoir ce qui se passe si un main standard est également présent, je n’ai pas vérifié cela ces dernières années):

 [D:\dev\test] 
> set link =/entry: mainCRTStartup
 
 [D:\dev\test] 
> cl bar.cpp user32.lib
 bar.cpp 
 LIBCMT.lib (crt0.obj): erreur LNK2019: symbole externe non résolu _main référencé dans la fonction ___ tmainCRTStartup 
 bar.exe: erreur fatale LNK1120: 1 externalités non résolues 
 
 [D:\dev\test] 
> set link =
 
 [D:\dev\test] 
> cl bar.cpp user32.lib
 bar.cpp 
 
 [D:\dev\test] 
> _ 

Avec une fonction de démarrage non standard telle que wmain, il est cependant probablement préférable de spécifier explicitement le point d'entrée, de manière à préciser le but recherché:

 [D:\dev\test] 
> cl bar.cpp/link user32.lib/entry: wmainCRTStartup
 bar.cpp 
 
 [D:\dev\test] 
> dumpbin/headers bar.exe | find/i "sous-système"
 6.00 Version du sous-système 
 3 sous-système (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 "sous-système"
 Version du sous-système 6.00 
 2 sous-système (interface graphique Windows) 
 
 [D:\dev\test] 
> _ 
157

Selon @RaymondChen

Le nom WinMain est juste une convention

Bien que la fonction WinMain soit documentée dans le Kit de développement Platform SDK, il s'agit de ne fait pas vraiment partie de la plate-forme. Au contraire, WinMain est le conventionnel nom du point d'entrée fourni par l'utilisateur à un programme Windows.

Le véritable point d’entrée est dans la bibliothèque d’exécution C, qui initialise le runtime, exécute des constructeurs globaux, puis appelle votre WinMain fonction (ou wWinMain si vous préférez un point d’entrée Unicode).

DllMain et WinMain sont différents dans leurs prototypes eux-mêmes. WinMain accepte les arguments de la ligne de commande pendant que l’autre parle de la manière dont il est attaché au processus.

Selon documentation MSDN

Par défaut, l'adresse de départ est un nom de fonction de la bibliothèque d'exécution C. L'éditeur de liens le sélectionne en fonction des attributs du programme, comme indiqué dans le tableau suivant.

  • mainCRTStartup (ou wmainCRTStartup) Une application utilisant /SUBSYSTEM:CONSOLE; appelle main (ou wmain)

  • WinMainCRTStartup (ou wWinMainCRTStartup) Une application utilisant /SUBSYSTEM:WINDOWS; appelle WinMain (ou wWinMain), qui doit être définie avec __stdcall

  • _DllMainCRTStartup Une DLL; appelle DllMain, qui doit être défini avec __stdcall, s'il existe

8
sarat

Un programme standard en C est passé 2 paramètres par la ligne de commande au démarrage:

int main( int argc, char** argv ) ;
  • char** argv est un tableau de chaînes (char*)
  • int argc est le numéro de char* dans argv

La fonction de démarrage WinMain que les programmeurs doivent écrire pour un programme Windows est légèrement différente. WinMain prend 4 paramètres qui sont transmis au programme par Win O/S au démarrage:

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.

Voir mon article Comment créer une fenêtre de base en C pour plus

2
bobobobo

Je me souviens vaguement d'avoir lu quelque part que les programmes Windows ont une fonction main(). Il est juste caché quelque part dans un en-tête ou une bibliothèque. Je crois que cette fonction main() initialise toutes les variables nécessaires à WinMain() et l’appelle ensuite.

Bien sûr, je suis un noob WinAPI et j'espère donc que les personnes plus compétentes sauront me corriger si je me trompe.

1
Code-Apprentice

J'ai eu un exe en utilisant _tWinMain et les propriétés de configuration.Linker.System.Subsem: Windows (/ SUBSYSTEM: WINDOWS). Plus tard, je voulais qu'il supporte les arguments de cmdline et qu'il soit imprimé sur la console. J'ai donc ajouté:

// We need to printf to stdout and we don't have one, so get one
AllocConsole();
// Redirect pre-opened STDOUT to the console
freopen_s((FILE **)stdout, "CONOUT$", "w", stdout);

mais cela ne fonctionnait qu'en imprimant dans une autre fenêtre de console qui s'en allait, donc ce n'était pas si utile. Voici comment je l'ai modifié pour qu'il fonctionne avec Console (/ SUBSYSTEM: CONSOLE) de manière à pouvoir effectuer des va-et-vient, si nécessaire.

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  UNREFERENCED_PARAMETER(envp);
  return (_tWinMain(NULL, NULL, ::GetCommandLineW(), 0));
}
0
markyoung