C++ 中的 WINMAIN 和 main()(扩展)

2021-12-05 00:00:00 windows main c++ winmain

对了,我看过这个帖子:WinMain 的区别,C++中的main和DllMain

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

我的意思是将不同的电源功能分开来启动程序有什么意义?是因为性能问题吗?或者是什么?

解决方案

关于功能.

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

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

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

作为C++中唯一这样的函数main有一个默认结果值,即0.如果main返回则在普通函数返回之后exitmain 结果值作为参数被调用.该标准定义了三个保证可以使用的值:0(表示成功)、EXIT_SUCCESS(也表示成功,通常定义为 0)和 EXIT_FAILURE(表示失败),其中两个命名常量由 <stdlib.h> 标头定义,该标头也声明了 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 的启动函数来扩展 C 和 C++ 语言,该函数具有编码为 UTF-16 的基于宽字符的参数strong>,可以代表任何文件名.

wmain 函数可以有 其中一个签名,对应于main的标准签名:

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

还有一些不是特别有用的.

即,wmainmain 的基于宽字符的直接替换.

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

int 回调 WinMain(HINSTANCE 实例,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);

其中 CALLBACKHINSTANCELPSTR 标头(LPSTR 只是 char*).

参数:

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

  • hPrevInstance 参数始终为 0,

  • lpCmdLine 参数也可以从 GetCommandLine API 函数中获取,加上一些奇怪的逻辑来跳过命令行的程序名称部分,和

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

因此,WinMain 函数与标准的 main 有相同的缺点,加上一些(特别是冗长和非标准),并且没有自己的优点,所以它真的莫名其妙,除非可能是供应商锁定的事情.然而,使用 Microsoft 工具链,它使链接器默认为 GUI 子系统,有些人认为这是一个优势.但是,例如GNU 工具链它没有这样的效果,所以不能依赖这个效果.

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

int WINAPI wWinMain(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

#undef UNICODE#define UNICODE#include int main(){MessageBox( 0, L"按确定", L"嗨", MB_SETFOREGROUND );}

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

对于控制台程序,Windows 会在需要时自动创建一个控制台窗口.任何 Windows 进程,不管子系统如何,都可以有一个关联的控制台窗口,最多一个.此外,Windows 命令解释器等待控制台程序完成,以便程序的文本呈现完成.

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

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

main 使用 GNU 工具链.

<前>[D:开发测试]> g++ foo.cpp[D:开发测试]> objdump -x a.exe |找到/i "subsys"主要子系统版本 4次要子系统版本 0子系统 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:开发测试]> g++ foo.cpp -mwindows[D:开发测试]> objdump -x a.exe |找到/i "subsys"主要子系统版本 4次要子系统版本 0子系统 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:开发测试]> _

main 与微软的工具链:

<前>[D:开发测试]> 设置 LINK=/entry:mainCRTStartup[D:开发测试]> cl foo.cpp user32.lib文件[D:开发测试]> dumpbin/headers foo.exe |找到/i "subsys"6.00 子系统版本3 子系统(Windows CUI)[D:开发测试]> cl foo.cpp/link user32.lib/subsystem:windows文件[D:开发测试]> dumpbin/headers foo.exe |找到/i "subsys"6.00 子系统版本2 子系统(Windows GUI)[D:开发测试]> _

<小时>

使用 Microsoft wmain 函数的示例.

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

   bar.cpp

#undef UNICODE#define UNICODE#include #include <字符串>//std::wstring#include //std::wostringstream使用命名空间标准;int wmain( int argc, wchar_t* argv[] ){wostringstream 文本;文本<

wmain 使用 GNU 工具链.

GNU 工具链不支持微软的 wmain 功能:

<前>[D:开发测试]> g++ bar.cppd:/bin/mingw/bin/../lib/gcc/i686-pc-mingw32/4.7.1/../../../libmingw32.a(main.o):main.c:(.text.startup+0xa3):对`WinMain 的未定义引用@16'collect2.exe:错误:ld 返回 1 个退出状态[D:开发测试]> _

这里的链接错误信息,关于WinMain,是因为GNU工具链确实支持那个函数(大概是因为太多古老的代码使用它),并搜索它作为未能找到标准 main 后的最后手段.

然而,添加一个具有标准 main 的模块是微不足道的,该模块调用 wmain:

   wmain_support.cpp

extern int wmain( int, wchar_t** );#undef UNICODE#define UNICODE#include //GetCommandLine, CommandLineToArgvW, LocalFree#include //退出失败int main(){结构参数{国际n;wchar_t** p;~Args() { if( p != 0 ) { ::LocalFree( p );} }Args(): p( ::CommandLineToArgvW( ::GetCommandLine(), &n ) ) {}};args args;如果( args.p == 0 ){返回 EXIT_FAILURE;}返回 wmain( args.n, args.p );}

现在,

<前>[D:开发测试]> g++ bar.cpp wmain_support.cpp[D:开发测试]> objdump -x a.exe |找到/i子系统"主要子系统版本 4次要子系统版本 0子系统 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:开发测试]> g++ bar.cpp wmain_support.cpp -mwindows[D:开发测试]> objdump -x a.exe |找到/i子系统"主要子系统版本 4次要子系统版本 0子系统 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:开发测试]> _

wmain 使用 Microsoft 的工具链.

如果没有指定入口点并且存在 wmain 函数(不清楚如果标准 main 也存在,近年来我没有检查过):

<前>[D:开发测试]> set link=/entry:mainCRTStartup[D:开发测试]> cl bar.cpp user32.libbar.cppLIBCMT.lib(crt0.obj):错误 LNK2019:未解析的外部符号 _main 在函数 ___tmainCRTStartup 中引用bar.exe:致命错误 LNK1120:1 未解析的外部[D:开发测试]> 设置链接=[D:开发测试]> cl bar.cpp user32.libbar.cpp[D:开发测试]> _

对于诸如 wmain 之类的非标准启动函数,最好明确指定入口点,以便非常清楚其意图:

<前>[D:开发测试]> cl bar.cpp/link user32.lib/entry:wmainCRTStartupbar.cpp[D:开发测试]> dumpbin/headers bar.exe |找到/i子系统"6.00 子系统版本3 子系统(Windows CUI)[D:开发测试]> cl bar.cpp/link user32.lib/entry:wmainCRTStartup/subsystem:windowsbar.cpp[D:开发测试]> dumpbin/headers bar.exe |找到/i子系统"6.00 子系统版本2 子系统(Windows GUI)[D:开发测试]> _

Right, I have looked at this post: Difference between WinMain,main and DllMain in C++

I now know that WINMAIN is used for window applications and main() for consoles. But reading the post doesn't really tell me why exactly what is the difference.

I mean what's the point of having separating different mains functions to start of a program? Is it due to performance issues? Or what is it?

解决方案

About the functions.

The C and C++ standards require any program (for a “hosted” C or C++ implementation) to have a function called main, which serves as the program's startup function. The main function is called after zero-initialization of non-local static variables, and possibly but not necessarily (!, C++11 §3.6.2/4) this call happens after dynamic initialization of such variables. It can have one of the following signatures:

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

plus possible implementation-defined signatures (C++11 §3.6.1/2) except that the result type must be int.

As the only such function in C++ main has a default result value, namely 0. If main returns then after the ordinary function return exit is called with the main result value as argument. The standard defines three values that guaranteed can be used: 0 (indicates success), EXIT_SUCCESS (also indicates success, and is typically defined as 0), and EXIT_FAILURE (indicates failure), where the two named constants are defined by the <stdlib.h> header which also declares the exit function.

The main arguments are intended to represent the command line arguments for the command used to start the process. argc (argument count) is the number of items in the argv (argument values) array. In addition to those items argv[argc] is guaranteed to be 0. If argc > 0 – which is not guaranteed! – then argv[0] is guaranteed to either be a pointer to an empty string, or a pointer to the “name used to invoke the program”. This name may include a path, and it may be the name of the executable.

Using the main arguments to obtain the command line arguments works fine in *nix, because C and C++ originated with *nix. However, the de facto Windows standard for the encoding of the main arguments is Windows ANSI, which does not support general Windows filenames (such as, for a Norwegian Windows installation, filenames with Greek or Cyrillic characters). Therefore Microsoft chose to extend the C and C++ languages with a Windows-specific startup function called wmain, which has wide character based arguments encoded as UTF-16, which can represent any filename.

The wmain function can have one of these signatures, corresponding to the standard signatures for main:

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

plus a few more that are not especially useful.

I.e., wmain is a direct wide character based replacement for main.

The WinMain char based function was introduced with Windows, in the early 1980's:

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

where CALLBACK, HINSTANCE and LPSTR are defined by the <windows.h> header (LPSTR is just char*).

Arguments:

  • the hInstance argument value is the base address of the memory image of the executable, it's primarily used to load resources from the executable, and it can alternatively be obtained from the GetModuleHandle API function,

  • the hPrevInstance argument is always 0,

  • the lpCmdLine argument can alternatively be obtained from the GetCommandLine API function, plus a bit of weird logic to skip the program name part of the command line, and

  • the nCmdShow argument value can alternatively be obtained from the GetStartupInfo API function, but with modern Windows the first creation of a top level window does that automatically so it's not of any practical use.

Thus, the WinMain function has the same drawbacks as standard main, plus some (in particular the verbosity and being non-standard), and no advantages of its own, so it's really inexplicable except possibly as a vendor lock-in thing. However, with the Microsoft tool chain it makes the linker default to the GUI subsystem, which some see as an advantage. But with e.g. the GNU toolchain it does not have such an effect so this effect cannot be relied on.

The wWinMain wchar_t based function is a wide character variant of WinMain, in the same way as wmain is a wide character variant of standard main:

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

where WINAPI is the same as CALLBACK, and PWSTR is simply wchar_t*.

There is no good reason to use any of the non-standard functions except the least known and least supported of them, namely wmain, and then just for convenience: that this avoids using the GetCommandLine and CommandLineToArgvW API functions to pick up UTF-16 encoded arguments.

To avoid the Microsoft linker acting up (the GNU toolchain's linker doesn't), just set the LINK environment variable to /entry:mainCRTStartup, or specify that option directly. This is the Microsoft runtime library entry point function that, after some initialization, calls the standard main function. The other startup functions have corresponding entry point functions named in the same systematic way.


Examples of using the standard main function.

Common source code:

    foo.cpp

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

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

In the examples below (first with the GNU toolchain and then with the Microsoft toolchain) this program is first built as a console subsystem program, and then as a GUI subsystem program. A console subsystem program, or in short just a console program, is one that requires a console window. This is the default subsystem for all Windows linkers I've used (admittedly not a great many), possibly for all Windows linkers period.

For a console program Windows creates a console window automatically if needed. Any Windows process, regardless of subsystem, can have an associated console window, and at most one. Also, the Windows command interpreter waits for a console program program to finish, so that the program's text presentation has finished.

Conversely, a GUI subsystem program is one that doesn't require a console window. The command interpreter does not wait for a GUI subsystem program, except in batch files. One way to avoid the completion wait, for both kinds of program, is to use the start command. One way to present console window text from a GUI subsystem program is to redirect its standard output stream. Another way is to explicitly create a console window from the program's code.

The program's subsystem is encoded in the executable's header. It's not shown by Windows Explorer (except that in Windows 9x one could “quick view” an executable, which presented just about the same information as Microsoft's dumpbin tool now does). There is no corresponding C++ concept.

main with the GNU toolchain.

[D:dev	est]
> g++ foo.cpp

[D:dev	est]
> 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	est]
> g++ foo.cpp -mwindows

[D:dev	est]
> 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	est]
> _

main with Microsoft's toolchain:

[D:dev	est]
> set LINK=/entry:mainCRTStartup

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

[D:dev	est]
> dumpbin /headers foo.exe | find /i "subsys"
            6.00 subsystem version
               3 subsystem (Windows CUI)

[D:dev	est]
> cl foo.cpp /link user32.lib /subsystem:windows
foo.cpp

[D:dev	est]
> dumpbin /headers foo.exe | find /i "subsys"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:dev	est]
> _


Examples of using Microsoft’s wmain function.

The following main code is common to both the GNU toolchain and Microsoft toolchain demonstrations:

    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:
";
    for( int i = 1;  i < argc;  ++i )
    {
        text << "
[" << argv[i] << "]";
    }

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

wmain with the GNU toolchain.

The GNU toolchain doesn't support Microsoft's wmain function:

[D:dev	est]
> 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	est]
> _

The link error message here, about WinMain, is because the GNU toolchain does support that function (presumably because so much ancient code uses it), and searches for it as a last resort after failing to find a standard main.

However, it's trivial to add a module with a standard main that calls the 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 );
}

Now,

[D:dev	est]
> g++ bar.cpp wmain_support.cpp

[D:dev	est]
> 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	est]
> g++ bar.cpp wmain_support.cpp -mwindows

[D:dev	est]
> 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	est]
> _

wmain with Microsoft’s toolchain.

With Microsoft's toolchain the linker automatically infers the wmainCRTStartup entry point if no entry point is specified and a wmain function is present (it's unclear what happens if a standard main is also present, I haven't checked that in recent years):

[D:dev	est]
> set link=/entry:mainCRTStartup

[D:dev	est]
> 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	est]
> set link=

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

[D:dev	est]
> _

With a non-standard startup function such as wmain it is, however, probably best to specify the entry point explicitly, so as to be very clear about the intention:

[D:dev	est]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup
bar.cpp

[D:dev	est]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               3 subsystem (Windows CUI)

[D:dev	est]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup /subsystem:windows
bar.cpp

[D:dev	est]
> dumpbin /headers bar.exe | find /i "subsystem"
            6.00 subsystem version
               2 subsystem (Windows GUI)

[D:dev	est]
> _

相关文章