关于Windows:C ++中的WINMAIN和main()(扩展)

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

是的,我看了这篇文章:C ++中的WinMain,main和DllMain之间的区别

我现在知道WINMAIN用于窗口应用程序,main()用于控制台。 但是阅读这篇文章并不能真正告诉我为什么有什么区别。

我的意思是将不同的主函数分开来启动程序有什么意义? 是由于性能问题引起的吗? 还是什么


关于功能。

C和C ++标准要求任何程序(用于托管C或C ++实现)都具有称为main的功能,该功能用作程序的启动功能。在对非局部静态变量进行零初始化之后,会调用main函数,并且可能(但不一定)(!, C ++ 11§3.6.2/ 4)在对此类变量进行动态初始化之后进行此调用。它可以具有以下签名之一:

好。

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

加上可能的实现定义的签名(C ++ 11§3.6.1/ 2),但结果类型必须为int

好。

作为C ++中唯一这样的函数,main具有默认结果值,即0。如果返回main,则在普通函数返回之后,以main结果值作为参数调用exit。该标准定义了三个可以保证使用的值:0(表示成功),EXIT_SUCCESS(也表示成功,通常定义为0)和EXIT_FAILURE(表示失败),其中两个命名常量由定义。 标头,它也声明了exit函数。

好。

main自变量旨在表示用于启动进程的命令的命令行自变量。 argc(自变量计数)是argv(自变量值)数组中的项目数。除这些项目外,argv[argc]保证为0。如果argc> 0,则不能保证!然后确保argv[0]是指向空字符串的指针或指向用于调用程序的名称的指针。该名称可以包括路径,并且可以是可执行文件的名称。

好。

使用main自变量获取命令行自变量在* nix中可以正常工作,因为C和C ++起源于* nix。但是,用于main参数编码的事实上的Windows标准是Windows ANSI,它不支持常规Windows文件名(例如,对于挪威语Windows安装,使用希腊或西里尔字符的文件名)。因此,Microsoft选择使用称为wmain的Windows特定于Windows的启动函数来扩展C和C ++语言,该函数具有基于宽字符的自变量,编码为UTF-16,可以表示任何文件名。

好。

wmain函数可以具有以下签名之一,与main的标准签名相对应:

好。

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

再加上一些不是特别有用的。

好。

wmainmain的直接基于宽字符的替换。

好。

基于WinMain char的函数是Windows于1980年代初期引入的:

好。

1
2
3
4
5
6
int CALLBACK WinMain(
    HINSTANCE   hInstance,
    HINSTANCE   hPrevInstance,
    LPSTR       lpCmdLine,
    int         nCmdShow
    );

其中CALLBACKHINSTANCELPSTR标头定义(LPSTR只是char*)。

好。

参数:

好。

  • HINSTANCE参数值是可执行文件的内存映像的基址,它主要用于从可执行文件加载资源,也可以从GetModuleHandle API函数获得,

    好。

  • hPrevInstance参数始终为0,

    好。

  • 或者,可以从GetCommandLine API函数获得lpCmdLine参数,再加上一些奇怪的逻辑以跳过命令行的程序名部分,并且

    好。

  • 也可以从GetStartupInfo API函数获取nCmdShow参数值,但是对于现代Windows,首次创建顶级窗口会自动执行此操作,因此没有任何实际用途。

    好。

  • 好。

    因此,WinMain函数具有与标准main相同的缺点,外加某些缺点(特别是冗长和非标准),并且没有其自身的优点,因此,除了作为供应商锁定之外,它实际上是无法解释的事情。但是,使用Microsoft工具链,链接器将默认设置为GUI子系统,有些人将其视为一种优势。但是例如GNU工具链没有这种作用,因此不能依赖这种作用。

    好。

    基于wWinMain wchar_t的函数是WinMain的宽字符变体,与wmain是标准main的宽字符变体相同:

    好。

    1
    2
    3
    4
    5
    6
    int WINAPI wWinMain(
        HINSTANCE   hInstance,
        HINSTANCE   hPrevInstance,
        PWSTR       lpCmdLine,
        int         nCmdShow
        );

    其中WINAPICALLBACK相同,而PWSTR仅是wchar_t*

    好。

    除了最鲜为人知和最不支持的非标准函数,即wmain,没有充分的理由使用它们,然后只是为了方便:避免使用GetCommandLineCommandLineToArgvW API函数拾取UTF-16编码的参数。

    好。

    为了避免Microsoft链接程序起作用(GNU工具链的链接程序没有作用),只需将LINK环境变量设置为/entry:mainCRTStartup,或直接指定该选项。这是Microsoft运行时库入口点函数,在进行一些初始化后,将调用标准main函数。其他启动功能具有以相同系统方式命名的相应入口点功能。

    好。

    使用标准main函数的示例。

    通用源代码:

    好。

    foo.cpp

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    #undef UNICODE
    #define UNICODE
    #include <windows.h>

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

    在下面的示例中(首先使用GNU工具链,然后使用Microsoft工具链),该程序首先构建为控制台子系统程序,然后构建为GUI子系统程序。控制台子系统程序,或者简称为控制台程序,是需要控制台窗口的程序。这是我使用过的所有Windows链接程序(当然不是很多)的默认子系统,可能是所有Windows链接程序时期的默认子系统。

    好。

    对于控制台程序,Windows会根据需要自动创建一个控制台窗口。任何Windows进程,无论子系统如何,都可以有一个关联的控制台窗口,并且最多可以有一个。此外,Windows命令解释器会等待控制台程序完成,以便该程序的文本表示已经完成。

    好。

    相反,GUI子系统程序是不需要控制台窗口的程序。除了批处理文件外,命令解释器不等待GUI子系统程序。对于两种程序,避免完成等待的一种方法是使用start命令。从GUI子系统程序显示控制台窗口文本的一种方法是重定向其标准输出流。另一种方法是根据程序代码显式创建控制台窗口。

    好。

    程序的子系统编码在可执行文件的标头中。 Windows资源管理器未显示它(除了在Windows 9x中可以快速查看可执行文件,它提供的信息几乎与Microsoft的dumpbin工具现在所提供的信息相同)。没有相应的C ++概念。

    好。

    main与GNU工具链。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [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与Microsoft的工具链:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [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]
    > _

    使用Microsofts wmain函数的示例。

    以下主要代码对于GNU工具链和Microsoft工具链演示都是通用的:

    好。

    bar.cpp

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #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:
    "
    ;
        for( int i = 1;  i < argc;  ++i )
        {
            text <<"
    ["
    << argv[i] <<"]";
        }

        MessageBox( 0, text.str().c_str(), argv[0], MB_SETFOREGROUND );
    }

    wmain与GNU工具链。

    GNU工具链不支持Microsoft的wmain函数:

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    [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]
    > _

    此处有关WinMain的链接错误消息是因为GNU工具链确实支持该功能(大概是因为很多古老的代码都使用了该功能),并且在找不到标准main后对其进行了最后的搜索。

    好。

    但是,添加带有标准main并调用wmain的模块很简单:

    好。

    wmain_support.cpp

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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 );
    }

    现在,

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [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与Microsoft的工具链。

    使用Microsoft的工具链,如果未指定入口点并且存在wmain函数,则链接器会自动推断wmainCRTStartup入口点(尚不清楚如果还存在标准main会发生什么,我最近没有检查过年份):

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [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]
    > _

    但是,对于诸如wmain之类的非标准启动功能,最好是显式指定入口点,以便非常清楚其意图:

    好。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    [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]
    > _

    好。


    根据@RaymondChen

    WinMain名称只是一个约定

    Although the function WinMain is documented in the Platform SDK, it's
    not really part of the platform. Rather, WinMain is the conventional
    name for the user-provided entry point to a Windows program.

    The real entry point is in the C runtime library, which initializes
    the runtime, runs global constructors, and then calls your WinMain
    function (or wWinMain if you prefer a Unicode entry point).

    DllMain和WinMain的原型本身不同。 WinMain接受命令行参数,而另一个则讨论如何将其附加到进程中。

    根据MSDN文档

    默认情况下,起始地址是C运行时库中的函数名称。链接器根据程序的属性选择它,如下表所示。

    • mainCRTStartup(或wmainCRTStartup)使用
      /SUBSYSTEM:CONSOLE;调用main(或wmain)

    • WinMainCRTStartup(或wWinMainCRTStartup)使用
      /SUBSYSTEM:WINDOWS;调用WinMain(或wWinMain),该
      __stdcall定义

    • _DllMainCRTStartup一个DLL;调用DllMain,如果存在,则必须使用__stdcall进行定义


    标准C程序在启动时会通过命令行传递2个参数:

    1
    int main( int argc, char** argv ) ;
    • char** argv是字符串数组(char*)
    • int argc是argv中char*的数量

    程序员必须为Windows程序编写的引导功能WinMain略有不同。 WinMain带有4个参数,这些参数在启动时由Win O / S传递给程序:

    1
    2
    3
    4
    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.

    有关更多信息,请参见我的文章如何在C中创建基本窗口。


    我隐约记得在某处Windows程序具有main()功能的地方。它只是隐藏在标头或库中的某个位置。我相信这个main()函数会初始化WinMain()所需的所有变量,然后调用它。

    当然,我是WinAPI的新手,所以我希望其他知识渊博的人能够在我输入错误的情况下纠正我。


    我有一个使用_tWinMain和Configuration Properties.Linker.System.Subsystem:Windows(/ SUBSYSTEM:WINDOWS)的exe。后来我希望它支持cmdline args并打印到控制台,所以我添加了:

    1
    2
    3
    4
    // 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);

    但这只能通过在另一个已消失的控制台窗口中进行打印来实现,因此没有什么用。下面是我将其更改为与控制台(/ SUBSYSTEM:CONSOLE)配合使用的方式,可以根据需要进行往返操作。

    1
    2
    3
    4
    5
    6
    7
    int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
    {
      UNREFERENCED_PARAMETER(argc);
      UNREFERENCED_PARAMETER(argv);
      UNREFERENCED_PARAMETER(envp);
      return (_tWinMain(NULL, NULL, ::GetCommandLineW(), 0));
    }