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?
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 WinMain
char
a été introduite avec Windows au début des années 1980:
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
où 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 wWinMain
wchar_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
);
où 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.
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] > _
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] > _
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
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 argvLa 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
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.
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));
}