webentwicklung-frage-antwort-db.com.de

WINMAIN und main () in C++ (Extended)

Richtig, ich habe diesen Beitrag angesehen: Unterschied zwischen WinMain, main und DllMain in C++

Ich weiß jetzt, dass WINMAIN für Fensteranwendungen und main() für Konsolen verwendet wird. Aber das Lesen des Beitrags sagt mir nicht wirklich, warum genau der Unterschied ist.

Ich meine, was bringt es, verschiedene Hauptfunktionen zu trennen, um ein Programm zu starten? Liegt es an Leistungsproblemen? Oder was ist das?

45
Danny

Über die Funktionen.

Die C- und C++ - Standards setzen voraus, dass ein Programm (für eine gehostete C- oder C++ - Implementierung) eine Funktion namens main besitzt, die als startup-Funktion des Programms dient. Die main-Funktion wird nach der zero-Initialisierung von nichtlokalen statischen Variablen aufgerufen, und möglicherweise (!, C++ 11 §3.6.2/4) geschieht dieser Aufruf nach der dynamischen Initialisierung von solche Variablen. Es kann eine der folgenden Signaturen haben:

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

plus mögliche implementierungsdefinierte Signaturen (C++ 11 §3.6.1/2), mit der Ausnahme, dass der Ergebnistyp int sein muss.

Als einzige solche Funktion in C++ hat main den Wert default result, nämlich 0. Wenn main zurückgegeben wird, wird nach dem Aufruf der gewöhnlichen Funktion exit mit dem Ergebniswert main als Argument aufgerufen. Der Standard definiert drei Werte, die garantiert verwendet werden können: 0 (zeigt Erfolg an), EXIT_SUCCESS (zeigt ebenfalls Erfolg an und wird normalerweise als 0 definiert) und EXIT_FAILURE (zeigt Fehler an), wobei die beiden Namen genannt werden Konstanten werden durch den <stdlib.h>-Header definiert, der auch die exit-Funktion deklariert.

Die main-Argumente sollen die Befehlszeilenargumente für den Befehl darstellen, der zum Starten des Prozesses verwendet wird. argc (Argumentanzahl) ist die Anzahl der Elemente im Array argv (Argumentwerte). Zusätzlich zu diesen Elementen ist argv[argc] garantiert 0. Wenn argc> 0 ist - was nicht garantiert ist! - dann ist argv[0] garantiert entweder ein Zeiger auf eine leere Zeichenfolge oder ein Zeiger auf den "Namen, mit dem das Programm aufgerufen wurde". Dieser Name kann einen Pfad enthalten und kann der Name der ausführbaren Datei sein.

Die Verwendung der main-Argumente zum Abrufen der Befehlszeilenargumente funktioniert in * nix problemlos, da C und C++ mit * nix entstanden sind. Jedoch die de facto Der Windows-Standard für die Kodierung der main-Argumente ist Windows ANSI, der keine allgemeinen Windows-Dateinamen unterstützt (z. B. bei einer norwegischen Windows-Installation Dateinamen mit griechischen oder kyrillischen Zeichen). Daher entschied sich Microsoft für die Erweiterung der C- und C++ - Sprachen um eine Windows-spezifische Startfunktion namens wmain, die breite zeichenbasierte Argumente enthält, die als UTF-16 codiert sind und einen beliebigen Dateinamen darstellen können.

Die wmain-Funktion kann eine dieser Signaturen haben, entsprechend den Standardsignaturen für main:

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

plus ein paar mehr, die nicht besonders nützlich sind.

Das heißt, wmain ist ein direkter zeichenbasierter Ersatz für main.

Die auf WinMainchar basierende Funktion wurde Anfang der 80er Jahre mit Windows eingeführt:

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

wobei CALLBACK, HINSTANCE und LPSTR durch den <windows.h>-Header definiert werden (LPSTR ist nur char*).

Argumente:

  • der Argumentwert hInstance ist die Basisadresse des Speicherabbilds der ausführbaren Datei. Er wird hauptsächlich zum Laden von Ressourcen aus der ausführbaren Datei verwendet. Alternativ kann er von der GetModuleHandle-API-Funktion abgerufen werden.

  • das hPrevInstance-Argument ist immer 0.

  • das lpCmdLine-Argument kann alternativ von der GetCommandLine-API-Funktion abgerufen werden. Außerdem enthält es eine seltsame Logik, um den Programmnamen der Befehlszeile zu überspringen

  • der nCmdShow-Argumentwert kann alternativ von der GetStartupInfo-API-Funktion abgerufen werden. Bei modernen Windows wird dies jedoch bei der ersten Erstellung eines Fensters der obersten Ebene automatisch ausgeführt, so dass es praktisch nicht von Nutzen ist.

Daher hat die WinMain-Funktion die gleichen Nachteile wie der Standard main, plus einige (insbesondere die Ausführlichkeit und Nicht-Standard) und keine eigenen Vorteile. Daher ist es wirklich unerklärlich, es sei denn, es handelt sich um eine Verkäufer-Locking-Funktion. Bei der Microsoft-Toolkette wird der Linker jedoch standardmäßig zum GUI-Subsystem gesetzt, was einige als Vorteil sehen. Aber mit z. Die Toolkette GNU hat keinen solchen Effekt, daher kann dieser Effekt nicht herangezogen werden.

Die auf wWinMainwchar_t basierende Funktion ist eine weite Zeichenvariante von WinMain, ebenso wie wmain eine weite Zeichenvariante des Standards main:

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

dabei ist WINAPI das gleiche wie CALLBACK und PWSTR einfach wchar_t*.

Es gibt keinen triftigen Grund, eine der nicht standardmäßigen Funktionen zu verwenden, außer den am wenigsten bekannten und am wenigsten unterstützten, nämlich wmain und dann nur noch der Bequemlichkeit halber, dass die Verwendung der GetCommandLine- und CommandLineToArgvW-API-Funktionen zum Aufnehmen von UTF-16-kodiertem Code vermieden wird Argumente.

Um zu verhindern, dass der Microsoft-Linker aktiv wird (der Linker der Toolkette GNU), setzen Sie einfach die Umgebungsvariable LINK auf /entry:mainCRTStartup oder geben Sie diese Option direkt an. Dies ist die Einstiegspunktfunktion der Microsoft-Laufzeitbibliothek, die nach einiger Initialisierung die Standardfunktion main aufruft. Die anderen Startfunktionen verfügen über entsprechende Einstiegspunktfunktionen, die auf die gleiche systematische Weise benannt werden.Beispiele für die Verwendung der Standardfunktion main.


Gemeinsamer Quellcode:

foo.cpp.

#undef UNICODE #define UNICODE #include <windows.h> int main() { MessageBox( 0, L"Press OK", L"Hi", MB_SETFOREGROUND ); }

In den folgenden Beispielen (zuerst mit der Toolkette GNU und dann mit der Microsoft-Toolchain) wird dieses Programm zunächst als console-Subsystem-Programm und dann als GUI-Subsystem-Programm erstellt. Ein Konsolen-Subsystem-Programm oder kurz nur ein console-Programm erfordert ein Konsolenfenster. Dies ist das Standard-Subsystem für alle Windows-Linker, die ich verwendet habe (allerdings nicht viele), möglicherweise für alle Windows-Linker.

start. Eine Möglichkeit, Konsolenfenster-Text aus einem GUI-Subsystem-Programm zu präsentieren, besteht darin, den Standardausgabestrom umzuleiten. Eine andere Möglichkeit besteht darin, ein Konsolenfenster explizit aus dem Programmcode zu erstellen.

Das Subsystem des Programms ist im Header der ausführbaren Datei codiert. Es wird nicht von Windows Explorer angezeigt (außer, dass man in Windows 9x eine ausführbare Datei „schnell anzeigen“ konnte, die genau die gleichen Informationen enthielt wie das dumpbin-Tool von Microsoft). Es gibt kein entsprechendes C++ - Konzept.

main mit der Toolkette GNU.

.

[D:\dev\test] >.

 [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] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000003 __Subsystem __
 [636] (sek -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] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000002 __System __
 [636] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version __

 [D:\dev\test] 
> _
 main

[D:\dev\test] >

 [D:\dev\test] 
> cl foo.cpp user32.lib
 foo.cpp 

 [D:\dev\test] 
> dumpbin/headers foo.exe | find/i "subsys"
 6.00 Subsystemversion 
 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 Subsystemversion 
 2 Subsystem (Windows GUI) 

 [D:\dev\test] 
> _
 Beispiele für die Verwendung der wmain

Der folgende Hauptcode ist für die Demonstrationen der GNU - Toolchain und Microsoft-Toolchain gleich:

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 ); }

.

[D:\dev\test] >

 d:/bin/mingw/bin /../lib/gcc/i686-pcmingw32/7.7.1/../../../../libmingw32.a (main.o): main.c :(. text.startup + 0xa3): undefinierter Verweis auf `WinMain 
 @ 16 '
 collect2.exe: Fehler: ld gab den Status 1 Exit 

 [D:\dev\test] zurück
> _
 die Verbindungsfehlermeldung hier zu WinMain

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 ); } .

Jetzt,

 [D:\dev\test] 
>

G ++ bar.cpp wmain_support.cpp

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] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000003 __System __
 [13689] (sek -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] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000002 __System __
 [13689] (sek -1) (fl 0x00) (ty 0) (scl 2) (nx 0) 0x00000000 __minor_subsystem_version __

 [D:\dev\test] 
> _
wmain

[D:\dev\test] >.

Set link =/entry: mainCRTStartup

cl bar.cpp user32.lib 
 bar.cpp 
 LIBCMT.lib (crt0.obj): Fehler LNK2019: nicht aufgelöstes externes Symbol _main in Funktion ___ tmainCRTStartup 
 bar.exe: schwerwiegender Fehler LNK1120: 1 nicht aufgelöste Externe .______. D:\dev\test] 
>set link = 

 [D:\dev\test] 
>cl bar.cpp user32.lib 
 bar.cpp 

 [D:\dev\test] 
> _
Bei einer nicht standardmäßigen Startup-Funktion wie wmain ist es jedoch wahrscheinlich am besten, den Einstiegspunkt explizit anzugeben, um die Absicht ganz klar zu machen: 
 [D:\dev\test] 
>

Cl bar.cpp/link user32.lib/entry: wmainCRTStartup

dumpbin/headers bar.exe | find/i "Subsystem" 
 6.00 Subsystemversion 
 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 Subsystemversion 
 2 Subsystem (Windows GUI) 

 [D:\dev\test] 
> _
dumpbin /headers bar.exe | find /i "subsystem"
152

Laut @RaymondChen

Der Name WinMain ist nur eine Konvention

Obwohl die Funktion WinMain im Platform SDK dokumentiert ist, ist es nicht wirklich ein Teil der Plattform. Vielmehr ist WinMain das konventionelle Name für den vom Benutzer angegebenen Einstiegspunkt für ein Windows-Programm.

Der eigentliche Einstiegspunkt befindet sich in der C-Laufzeitbibliothek, die .__ initialisiert. führt die Laufzeitumgebung globale Konstruktoren aus und ruft dann Ihre WinMain .__ auf. Funktion (oder wWinMain, wenn Sie einen Unicode-Einstiegspunkt bevorzugen).

DllMain und WinMain unterscheiden sich in ihren Prototypen. WinMain akzeptiert Kommandozeilenargumente, während das andere darüber spricht, wie es mit dem Prozess verbunden ist.

Laut MSDN-Dokumentation

Die Startadresse ist standardmäßig ein Funktionsname aus der C-Laufzeitbibliothek. Der Linker wählt ihn gemäß den Attributen des Programms aus, wie in der folgenden Tabelle gezeigt.

  • mainCRTStartup (oder wmainCRTStartup) Eine Anwendung, die /SUBSYSTEM:CONSOLE; verwendet, ruft main (oder wmain) auf.

  • WinMainCRTStartup (oder wWinMainCRTStartup) Eine Anwendung, die /SUBSYSTEM:WINDOWS;-Aufrufe WinMain (oder wWinMain) verwendet. Die muss mit __stdcall definiert sein.

  • _DllMainCRTStartup Eine DLL; Ruft DllMain auf, die mit __stdcall definiert werden muss, falls vorhanden

8
sarat

Ein Standard-C-Programm wird beim Start von der Befehlszeile mit 2 Parametern übergeben:

int main( int argc, char** argv ) ;
  • char** argv ist ein Array von Strings (char*)
  • int argc ist die Nummer von char* in argv

Die Boot-Funktion WinMain, die Programmierer für ein Windows-Programm schreiben müssen, unterscheidet sich geringfügig. WinMain benötigt 4 Parameter, die beim Start von Win O/S an das Programm übergeben werden:

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.

Siehe meinen Artikel Wie man ein einfaches Fenster in C erstellt, um mehr zu erfahren

2
bobobobo

Ich erinnere mich vage daran, irgendwo gelesen zu haben, dass Windows-Programme eine main()-Funktion haben. Es ist nur irgendwo in einem Header oder einer Bibliothek versteckt. Ich glaube, diese main()-Funktion initialisiert alle von WinMain() benötigten Variablen und ruft sie dann auf.

Natürlich bin ich ein WinAPI-Noob, also hoffe ich, dass andere, die mehr wissen, mich korrigieren, wenn ich falsch liege.

1
Code-Apprentice

Ich hatte ein Exe mit _tWinMain und Configuration Properties.Linker.System.Subsystem: Windows (/ SUBSYSTEM: WINDOWS). Später wollte ich, dass cmdline args unterstützt wird und auf der Konsole gedruckt wird. Ich fügte hinzu:

// 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);

aber das funktionierte nur, wenn in einem anderen Konsolenfenster gedruckt wurde, was wegging. Im Folgenden habe ich die Methode so geändert, dass sie mit Console (/ SUBSYSTEM: CONSOLE) so funktioniert, dass ich hin und her gehen kann, wenn es nötig ist.

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