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

Posted

技术标签:

【中文标题】C++ 中的 WINMAIN 和 main()(扩展)【英文标题】:WINMAIN and main() in C++ (Extended) 【发布时间】:2012-12-02 00:54:15 【问题描述】:

对,我看过这个帖子:Difference between WinMain,main and DllMain in C++

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

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

【问题讨论】:

取决于您想要传递到应用程序中的内容。根据标准,int main() 始终被允许作为入口点,但与 int main(int, char**) 一起。 【参考方案1】:

Windows CRT 公开了 5 个符号 mainCRTStartupwmainCRTStartupwWinMainCRTStartup_DllMainCRTStartupWinMainCRTStartup

前面的w表示unicode版本,即传递给进程的命令行(命令和参数字符串)(并存储在PPBPEB->ProcessParameters->CommandLine中)是UTF-16(即WCHAR Wide char)) 而不是 ASCII(UTF-8 或 ANSI,例如 Windows-1252、Windows-850 等)

如果未指定/DLL/SUBSYSTEM 选项,则MSVC 链接器选择子系统和入口点,即根据mainWinMainDllMain 将入口地址设为这5 个函数之一存在于其中一个目标文件的符号表中。入口点是statically linked by the MSVC linker via libcmt.lib,它将进一步只包含该入口函数实际使用的符号。

mainCRTStartup 将调用GetStartupInfo() / 访问 PPB 以获取标准输入/输出句柄和命令行参数。 _init term()_init_atexit() 也被称为。它调用mainmain的返回值在调用设置的atexit例程后传递给ExitProcess()

需要在 IDA Pro 中加载使用 /Zi 编译器选项创建的 .pdb 中的调试符号,以显示诸如 mainCRTstartupmain 之类的符号,这些符号将在没有调试符号的情况下改为表示为分别为startsub_??????

参考:

/ENTRY (Entry-Point Symbol) | Microsoft Docs § Remarks

【讨论】:

【参考方案2】:

根据@RaymondChen 的说法

The name WinMain is just a convention

虽然函数 WinMain 记录在 Platform SDK 中,但它是 不是平台的一部分。相反,WinMain 是传统的 用户提供的 Windows 程序入口点的名称。

真正的入口点在 C 运行时库中,它初始化 运行时,运行全局构造函数,然后调用你的 WinMain 函数(或 wWinMain,如果您更喜欢 Unicode 入口点)。

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

根据MSDN documentation

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

mainCRTStartup(或wmainCRTStartup)一个应用程序使用 /SUBSYSTEM:CONSOLE; 调用 main(或 wmain

WinMainCRTStartup(或wWinMainCRTStartup)一个应用程序使用 /SUBSYSTEM:WINDOWS; 调用WinMain(或wWinMain),必须是 用__stdcall定义

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

【讨论】:

【参考方案3】:

关于功能。

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 的宽字符参数,可以代表任何文件名。

wmain函数可以有one of these signatures,对应main的标准签名:

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

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

wmainmain 的直接宽字符替换。

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

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

其中CALLBACKHINSTANCELPSTR<windows.h> 标头(LPSTR is just char*) 定义。

参数:

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

hPrevInstance 参数始终为 0,

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

1234563 >

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

基于wWinMainwchar_t函数是WinMain的宽字符变体,就像wmain是标准main的宽字符变体一样:

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

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

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

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


使用标准main 函数的示例。

常用源码:

    foo.cpp

#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 工具链。

[D:\开发\测试] > g++ foo.cpp [D:\开发\测试] > objdump -x a.exe |查找 /i "子系统" 主要子系统版本 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 "子系统" 主要子系统版本 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 foo.cpp [D:\开发\测试] > dumpbin /headers foo.exe |查找 /i "子系统" 6.00 子系统版本 3 子系统 (Windows CUI) [D:\开发\测试] > cl foo.cpp /link user32.lib /subsystem:windows foo.cpp [D:\开发\测试] > dumpbin /headers foo.exe |查找 /i "子系统" 6.00 子系统版本 2 子系统(Windows GUI) [D:\开发\测试] > _

使用微软wmain函数的示例。

以下主要代码对 GNU 工具链和 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 使用 GNU 工具链。

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

[D:\开发\测试] > g++ bar.cpp d:/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工具链确实支持that功能(大概是因为很多古代代码都使用了它),然后搜索它作为最后的手段找不到标准的main

但是,使用标准 main 添加一个调用 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 );

现在,

[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 函数,链接器会自动推断 wmainCRTStartup 入口点(不清楚如果标准 main 也存在会发生什么,我还没有检查近年来):

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

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

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

【讨论】:

谁能更新有效winmain()签名的链接? @jvriesem: WinMain 只有一个有效签名,wWinMain 也是如此。但是,wmain 与标准 main 类似,只是具有宽字符串参数。这意味着它支持int wmain()int wmain( int, wchar_t** ) 像标准mainint wmain( int, wchar_t**, wchar** ) 作为扩展。 3ʳᵈ 参数,通常称为envp,是一个指向环境变量字符串的指针,其中根据current Microsoft docs,“此数组由 NULL 条目终止”。现在愚蠢的 SO 拒绝更多的文字,所以。【参考方案4】:

Main vs WinMain

正如我从许多链接中读到的:

WinMain() 是任何 Windows 应用程序的 C 入口点函数。就像普通的基于DOS/console 的应用程序具有main() 功能作为C 入口点,在Windows 中我们有WinMain()WinMain()是系统在创建进程时调用的函数。

第一个参数是当前进程的实例句柄。

下一个是上一个实例。

命令行参数作为下一个参数。

最后shell传递了主窗口的show/display属性。

注意:WinMain 将成功返回为零,错误返回非零。

【讨论】:

【参考方案5】:

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

// 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) 的方式,如果需要,我可以来回切换。

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);
  UNREFERENCED_PARAMETER(envp);
  return (_tWinMain(NULL, NULL, ::GetCommandLineW(), 0));

【讨论】:

【参考方案6】:

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

当然,我是一个WinAPI菜鸟,所以如果我错了,希望其他知识渊博的人纠正我。

【讨论】:

如果这是真的并且我的想法是正确的,这意味着我们可以通过模拟“隐藏的main()”的作用来隐藏没有-mwindows的控制台窗口。【参考方案7】:

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

int main( int argc, char** argv ) ;
char** argv 是一个字符串数组 (char*) int argc 是 argv 中 char* 的编号

程序员必须为windows程序编写的引导函数WinMain略有不同。 WinMain在启动时接受Win O/S传递给程序的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.

查看我的文章How to create a basic window in C了解更多

【讨论】:

-1 几个错误的说法:“必须写”,不。 “通过......通过 Win O/S”,不。除了一些术语:“启动功能”,不。 好吧,“必须写”是错误的,因为无论 Windows 子系统如何,您始终可以为应用程序使用标准的 main。 “通过......由 Win O/S”是错误的,因为这些参数不是由操作系统传递的。它们由入口点函数传递,该函数从操作系统获取信息,例如致电GetCommandLine。 “引导功能”具有误导性,因为不涉及引导。 在我看来答案是没有用的。 QA 问有什么区别,你说函数签名的区别。不是很明显吗?

以上是关于C++ 中的 WINMAIN 和 main()(扩展)的主要内容,如果未能解决你的问题,请参考以下文章

关于VC++的Winmain函数(WINAPI是啥?)

关于VC++的Winmain函数(WINAPI是啥?)

c++主函数如何调用"WinMain"函数

windows里边winmain中的四个参数分别是啥意思啊

从WinMain开始

未定义对“WinMain@16”的引用?