C++ 中的 WINMAIN 和 main()(扩展)
对了,我看过这个帖子: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
返回则在普通函数返回之后exit
以 main
结果值作为参数被调用.该标准定义了三个保证可以使用的值: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[] )
还有一些不是特别有用的.
即,wmain
是 main
的基于宽字符的直接替换.
WinMain
基于 char
的函数是在 1980 年代初期在 Windows 中引入的:
int 回调 WinMain(HINSTANCE 实例,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);
其中 CALLBACK
、HINSTANCE
和 LPSTR
由
标头(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);
其中 WINAPI
与 CALLBACK
相同,PWSTR
只是 wchar_t*
.
没有充分的理由使用任何非标准函数,除了最不为人所知和最不支持的函数,即 wmain
,然后只是为了方便:这避免使用 GetCommandLine
和 CommandLineToArgvW
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
功能:
这里的链接错误信息,关于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
也存在,近年来我没有检查过):
对于诸如 wmain
之类的非标准启动函数,最好明确指定入口点,以便非常清楚其意图:
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 theGetModuleHandle
API function,the
hPrevInstance
argument is always 0,the
lpCmdLine
argument can alternatively be obtained from theGetCommandLine
API function, plus a bit of weird logic to skip the program name part of the command line, andthe
nCmdShow
argument value can alternatively be obtained from theGetStartupInfo
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] > _
相关文章