72

对,我看过这篇文章:C++ 中 WinMain、main 和 DllMain 的区别

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

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

4

7 回答 7

200

关于功能。

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 - 不能保证!– thenargv[0]保证要么是指向空字符串的指针,要么是指向“用于调用程序的名称”的指针。此名称可能包含路径,也可能是可执行文件的名称。

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

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

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

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

即,wmain是基于宽字符的直接替换main.

在1980 年代初期,在 Windows 中引入了 based 函数:WinMain char

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

其中CALLBACK,HINSTANCELPSTR<windows.h>标头定义 ( LPSTRis justchar* )。

论据:

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

  • hPrevInstance参数始终为 0 ,

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

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

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

based 函数是 的宽字符变体,与 standard 的宽字符变体相同:wWinMain wchar_tWinMainwmainmain

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

whereWINAPI与 相同CALLBACK,并且PWSTR很简单wchar_t*

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

为避免 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:\开发\测试]
>垃圾箱/headers foo.exe | 查找 /i “子系统”
            6.00 子系统版本
               3 子系统 (Windows CUI)

[D:\开发\测试]
> cl foo.cpp /link user32.lib /subsystem:windows
foo.cpp

[D:\开发\测试]
>垃圾箱/headers foo.exe | 查找 /i “子系统”
            6.00 子系统版本
               2 子系统(Windows GUI)

[D:\开发\测试]
> _

使用微软wmain函数的例子。

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

    酒吧.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:\开发\测试]
> _

此处的链接错误消息 aboutWinMain是因为 GNU 工具链确实支持功能(可能是因为很多古老的代码都使用它),并在找不到标准后将其作为最后的手段进行搜索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与微软的工具链。

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

[D:\开发\测试]
>设置链接=/入口: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:\开发\测试]
>垃圾箱/标题栏.exe | 查找 /i “子系统”
            6.00 子系统版本
               3 子系统 (Windows CUI)

[D:\开发\测试]
> cl bar.cpp /link user32.lib /entry:wmainCRTStartup /subsystem:windows
酒吧.cpp

[D:\开发\测试]
>垃圾箱/标题栏.exe | 查找 /i “子系统”
            6.00 子系统版本
               2 子系统(Windows GUI)

[D:\开发\测试]
> _
于 2012-12-14T03:25:39.677 回答
11

根据@RaymondChen

WinMain 这个名字只是一个约定

尽管函数 WinMain 记录在 Platform SDK 中,但它并不是平台的真正组成部分。相反,WinMain 是用户提供的 Windows 程序入口点的常规名称。

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

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

根据MSDN 文档

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

  • mainCRTStartup(or wmainCRTStartup) 使用 /SUBSYSTEM:CONSOLE;调用 main (or wmain)的应用程序

  • WinMainCRTStartup(or wWinMainCRTStartup) 使用 /SUBSYSTEM:WINDOWS;调用WinMain(or ) 的应用程序wWinMain,必须使用__stdcall

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

于 2012-12-14T03:37:21.153 回答
3

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

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

WinMain程序员必须为 windows 程序编写 的引导函数略有不同。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.

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

于 2012-12-14T02:03:31.357 回答
2

Windows CRT 公开了 5 个符号mainCRTStartupwmainCRTStartupwWinMainCRTStartup和。_DllMainCRTStartupWinMainCRTStartup

在此处输入图像描述

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

如果未指定/DLLor选项,则 MSVC 链接器选择子系统和入口点,/SUBSYSTEM即根据或是否存在于目标文件之一的符号表中,使入口地址成为这 5 个函数之一。入口点由 MSVC 链接器通过 静态链接,这将进一步仅包含该入口函数实际使用的符号。mainWinMainDllMainlibcmt.lib

mainCRTStartup将调用GetStartupInfo()/访问 PPB 以获取标准输入/输出句柄和命令行参数。_init term()也称为,以及_init_atexit()。它调用set例程调用后main的返回值。mainExitProcess()atexit

/Zi需要在 IDA Pro 中加载使用编译器选项创建的 .pdb 中的调试符号,以显示mainCRTstartup和等符号main,在没有调试符号的情况下,它们将分别表示为startsub_??????

参考:

于 2020-04-07T00:50:39.727 回答
1

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

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

于 2012-12-14T02:09:45.863 回答
0

我有一个使用 _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));
}
于 2018-12-11T20:40:30.467 回答
0

主要与 WinMain

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

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

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

接下来是上一个实例。

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

最后 shell 传递主窗口的显示/显示属性。

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

于 2020-01-08T10:10:35.213 回答