解析Windows main和wWinMain函数

引言

Windows 创建不同的应用程序,所对应的函数入口也会有所不同,在此以main和wWinMain函数来分析一下为什么windows这么设计。其它类似main入口同理。

main函数

1. 首先创建一个windows控制台程序(如下所示),在源码处打个断点,点击调试

 2. 查看调用堆栈

 3. 右键外部代码,点击显示外部代码

 4. 点击invoke_main

可以看到windows对main函数外面做了一层封装,一直到mianCRTStartup函数,这一层调用代码称为启动代码,是windows操作系统和程序的桥梁 。mainCRTStartup再之前就是内核代码了。

wWinMain函数

1. 创建windows桌面应用程序,在源码处打个断点,点击调试

2. 与上面同理,查看堆栈,可以看到堆栈与main函数貌似是一样的?继续往下看

 3. 我们看下invoke_main源码


#if defined _SCRT_STARTUP_MAIN

    using main_policy = __scrt_main_policy;
    using file_policy = __scrt_file_policy;
    using argv_policy = __scrt_narrow_argv_policy;
    using environment_policy = __scrt_narrow_environment_policy;

    static int __cdecl invoke_main()
    {
        return main(__argc, __argv, _get_initial_narrow_environment());
    }

#elif defined _SCRT_STARTUP_WMAIN

    using main_policy = __scrt_main_policy;
    using file_policy = __scrt_file_policy;
    using argv_policy = __scrt_wide_argv_policy;
    using environment_policy = __scrt_wide_environment_policy;

    static int __cdecl invoke_main()
    {
        return wmain(__argc, __wargv, _get_initial_wide_environment());
    }

#elif defined _SCRT_STARTUP_WINMAIN

    using main_policy = __scrt_winmain_policy;
    using file_policy = __scrt_file_policy;
    using argv_policy = __scrt_narrow_argv_policy;
    using environment_policy = __scrt_narrow_environment_policy;

    static int __cdecl invoke_main()
    {
        return WinMain(
            reinterpret_cast<HINSTANCE>(&__ImageBase),
            nullptr,
            _get_narrow_winmain_command_line(),
            __scrt_get_show_window_mode());
    }

#elif defined _SCRT_STARTUP_WWINMAIN

    using main_policy = __scrt_winmain_policy;
    using file_policy = __scrt_file_policy;
    using argv_policy = __scrt_wide_argv_policy;
    using environment_policy = __scrt_wide_environment_policy;

    static int __cdecl invoke_main()
    {
        return wWinMain(
            reinterpret_cast<HINSTANCE>(&__ImageBase),
            nullptr,
            _get_wide_winmain_command_line(),
            __scrt_get_show_window_mode());
    }

#elif defined _SCRT_STARTUP_ENCLAVE || defined _SCRT_STARTUP_WENCLAVE

    using main_policy = __scrt_enclavemain_policy;
    using file_policy = __scrt_nofile_policy;
    using argv_policy = __scrt_no_argv_policy;
    using environment_policy = __scrt_no_environment_policy;

#if defined _SCRT_STARTUP_ENCLAVE
    static int __cdecl invoke_main()
    {
        return main(0, nullptr, nullptr);
    }
#else
    static int __cdecl invoke_main()
    {
        return wmain(0, nullptr, nullptr);
    }
#endif

#endif

这里可以看出不同的宏定义导致了执行不同的入口函数。这些宏在哪定义的?点击堆栈中的wWinMainCRTStartup函数(如下图),两个程序的入口cpp文件是不一样的。这里就决定了调用main函数还是wWinMain函数。

结论

以上我们可以看到main和wWinMain函数的整个调用过程,为什么要这么处理,看各个入口函数的参数就能看出,因为不同类型的应用程序有不同的要求,需要不同的环境来运行和管理。

举例来说,控制台应用程序主要通过命令行输入和输出与用户交互,因此它们需要使用 "main" 函数来读取命令行参数和输出结果到控制台。另一方面,GUI(图形用户界面)应用程序则需要与用户通过图形界面进行交互,因此它们需要使用 "WinMain" 函数来启动和管理窗口,处理消息和事件等。

其它

Q:windows程序的启动函数mainCRTStartup,如何被调用的

A: 在 Windows 操作系统中,C/C++ 程序的启动函数名为 mainCRTStartup。这个函数是由 C 运行时库(CRT)提供的,它负责初始化 CRT 并调用程序的 main 函数,从而启动程序。

在 Windows 操作系统中,mainCRTStartup 函数是通过链接器(Linker)在程序的可执行文件中生成的。当用户双击可执行文件或使用命令行启动程序时,操作系统会创建一个新的进程,并在进程的上下文中执行可执行文件中的代码。在进程启动时,操作系统会将控制权转移到 mainCRTStartup 函数,并开始执行它的代码。

mainCRTStartup 函数的主要作用是执行一系列初始化操作,包括:

  1. 初始化 CRT 运行时环境,包括内存分配器、文件 I/O 等。

  2. 解析命令行参数,并将它们传递给程序的 main 函数。

  3. 调用程序的 main 函数,并将 main 函数的返回值作为程序的退出代码。

当 mainCRTStartup 函数执行完毕后,程序的执行流程将转移到 CRT 库的退出函数(如 exit 函数),执行一些清理操作,最终退出程序并返回操作系统。

总之,mainCRTStartup 函数是 Windows 程序的启动函数,它由 CRT 运行时库提供,并由链接器生成在程序的可执行文件中。操作系统会在进程启动时调用 mainCRTStartup 函数,它执行一系列初始化操作,最终将控制权转移给程序的 main 函数,启动程序的执行。

Q: 为什么上面有.nil文件而不是.cpp和.h文件

A: 通常情况下,.nil文件是用于内联函数的头文件,而不是普通的头文件。内联函数是一种函数,当编译器遇到内联函数时,它会在编译时将函数的代码直接嵌入到调用该函数的代码中,而不是像普通函数那样需要调用函数时跳转到函数的代码段执行。这样就避免了函数调用时的开销,提高了程序的运行效率。因此,内联函数通常适用于函数体较短、频繁调用、对性能要求较高的场景。

Q: 内联函数和宏定义的区别

A: 宏定义是预处理阶段将函数的代码嵌入到调用处,内联函数是编译阶段将函数嵌入到调用处,以避免函数调用的开销。它们之间的区别:

  1. 类型安全:内联函数可以进行类型检查,从而保证类型安全;而宏定义则没有类型检查,容易产生潜在的类型错误。

  2. 可读性:内联函数的代码比较清晰易读,能够提高代码的可读性;而宏定义的代码则可能比较复杂,可读性较差。

  3. 调试:内联函数可以像普通函数一样进行调试,方便程序员进行调试和排错;而宏定义则不支持调试,出错时难以定位。

  4. 宏定义支持的功能更多,比如可以定义包含多个语句的代码块等。

需要注意的是,使用内联函数和宏定义时需要权衡其优缺点,并根据实际情况进行选择。在一些场合,内联函数会比宏定义更加安全、可读、易于调试;而在另一些场合,宏定义则可能更加方便、灵活、功能强大。

参考资料

https://chat.openai.com/

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值