【问题标题】:What functions does _WinMainCRTStartup perform?_WinMainCRTStartup 执行什么功能?
【发布时间】:2010-12-07 16:18:12
【问题描述】:

这是一系列至少两个密切相关但又截然不同的问题的一部分。我希望通过单独询问他们是正确的。

我正在尝试让我的 Visual C++ 2008 应用程序在没有 C 运行时库的情况下工作。这是一个 Win32 GUI 应用程序,没有 MFC 或其他花哨的东西,只是普通的 Windows API。

所以我将 Project Properties -> Configuration -> C/C++ -> Advanced -> Omit Default Library Names 设置为 Yes (compiler flag /Zl) 并重建。

然后链接器抱怨未解析的外部_WinMainCRTStartup。很公平,我可以告诉链接器使用不同的入口点,比如MyStartup。根据我在网上收集到的信息,_WinMainCRTStartup 做了一些初始化工作,我可能希望 MyStartup 做其中的一部分。

所以我的问题是:_WinMainCRTStartup 执行哪些功能,如果我不使用 CRT,我可以省略哪些功能?

如果你对这些东西很了解,也请看看my other question。谢谢!

旁白:我为什么要首先这样做?

  1. 我的应用没有明确使用任何 CRT 函数。
  2. 我喜欢精益求精的应用。
  3. 它会教给我一些新东西。

【问题讨论】:

    标签: visual-c++ crt entry-point winmain


    【解决方案1】:

    CRT 的入口点执行以下操作(此列表不完整):

    • 初始化 CRT 所需的全局状态。如果不这样做,您将无法使用 CRT 提供的任何功能或状态。
    • 初始化编译器使用的一些全局状态。运行时检查(例如 /GS 使用的安全 cookie)在这里绝对是突出的。不过,您可以自己致电__security_init_cookie。您可能需要为其他运行时检查添加其他代码。
    • 调用 C++ 对象的构造函数。如果您正在编写 C++ 代码,则可能需要对此进行模拟。
    • 检索操作系统提供的命令行和启动信息,并将其传递给您的主程序。默认情况下,操作系统不会向程序的入口点传递任何参数 - 它们都是由 CRT 提供的。

    CRT 源代码在 Visual Studio 中可用,您可以在调试器中单步调试 CRT 的入口点,并准确了解它在做什么。

    【讨论】:

    • 谢谢,我不知道源是开放的。似乎使我的问题过时了。您确定 C++ 构造函数吗?我似乎找不到那个。 (这并不重要;我认为这仅适用于全局变量?)
    • 我相信 C++ 构造函数在 __initterm 中。这是从记忆中得出的,可能是错误的,但是 C++ 构造函数会在某些数据部分中注册,并且 __initterm 会遍历它。
    【解决方案2】:

    true 用 C(不是 C++)编写的 Win32 程序根本不需要任何初始化,因此您可以使用 WinMainCRTStartup() 而不是 WinMain(HINSTANCE,...).

    将控制台程序编写为真正的 Win32 应用程序也是可能的,但有点困难;入口点的默认名称是 _mainCRTStartup()。

    禁用所有额外的代码生成功能,如堆栈探测、数组检查等。仍然可以进行调试。

    初始化

    有时您需要第一个 HINSTANCE 参数。对于Win32(Win32s除外),固定为(HINSTANCE)0x400000。

    nCmdShow 参数始终为 SW_SHOWDEFAULT。

    如有必要,使用 GetCommandLine() 检索命令行。

    终止

    当您的程序产生线程时,例如通过调用 GetOpenFileName(),使用 return 关键字从 WinMainCRTStartup() 返回将挂起您的程序 — 使用 ExitProcess() 代替。

    注意事项

    在以下情况下你会遇到相当大的麻烦:

    • 使用大于 4 KB(每个函数)的堆栈帧(即局部变量)
    • 使用浮点运算(例如 float->int 转换)
    • 在 32 位机器上使用 64 位整数(乘法、位移运算)
    • 使用 C++ new、delete 和具有非零所有成员构造函数的静态对象
    • 当然要使用 fopen()、printf() 等标准库函数

    疑难解答

    所有 Windows 系统(自 Windows 95 起)都有一个 C 标准库,即 MSVCRT.DLL。

    要使用它,请导入它们的入口点,例如使用我的 msvcrt-light.lib(谷歌)。但仍有一些注意事项,尤其是在使用比 MSVC6 更新的编译器时:

    • 堆栈帧仍限制为 4 KB
    • _ftol_sse 或 _ftol2_sse 必须路由到 _ftol
    • _iob_func 必须路由到 _iob

    它的初始化似乎在加载时运行。至少文件功能会无缝运行。

    【讨论】:

    • 那是什么:将 C++ 的 new、delete 和 static 对象与非零出所有成员的构造函数一起使用
    • 对于 Win32(Win32s 除外),固定为 (HINSTANCE)0x400000。 - 这不是真的。
    • 这是非常古老的,但这些信息将无限期地帮助我。您如何将 _ftol_sse 和 _ftol2_see 路由到 _ftol?我一直在尝试将浮点数转换为整数时遇到这个问题,我最终创建了一个 sse2 函数来执行此操作,然后将结果转换为 int。它似乎避免了对 _ftol2_sse 的调用,但这不是我想要的。
    • @Xearinox:没错。实际上,模块的HINSTANCE 只是一个指向模块基地址的指针(/BASE 链接器参数);提到的地址只是可执行文件的默认,因为没有发生 ASLR。
    【解决方案3】:

    老问题,但答案要么不正确,要么只针对一个特定问题。

    如果程序实际上是从 main/WinMain 启动的,那么有许多 C 和 C++ 功能在 Windows(或大多数操作系统)上根本不可用。

    举个简单的例子:

    class my_class
    {
    public:
        my_class() { m_val = 5; }
        int my_func(){ return m_val }
    private:
        int m_val;
    }
    
    my_class g_class;
    
    int main(int argc, char **argv)
    {
         return g_class.my_func();
    }
    

    为了让这个程序按预期运行,my_class 的构造函数必须在 main 之前调用。如果程序恰好从 main 开始,则需要编译器 hack(注意:GCC 在某些情况下会这样做)在 main 的开头插入函数调用。相反,在大多数操作系统上,在大多数情况下,一个不同的函数构造 g_class,然后调用 main(在 Windows 上,这是 mainCRTStartup 或 WinMainCRTStartup;在大多数其他操作系统上,我习惯使用一个名为 _start 的函数)。

    C++ 甚至 C 还需要在 main 工作之前或之后完成其他事情。 一旦 main 启动,stdin 和 stdout(std::cin 和 std::cout)如何可用? atexit 是如何工作的?

    C 标准要求标准库具有类似 POSIX 的信号 API,在 Windows 上必须在 main() 之前“安装”。

    在大多数操作系统上,没有系统提供的堆; C 运行时实现自己的堆(微软的 C 运行时只是包装了 Kernel32 堆函数)。

    即使是传递给 main、argc 和 argv 的参数,也必须以某种方式从系统中获取。

    您可能想看看 Matt Pietrick 的(古老的)关于实现他自己的 C 运行时的文章,以了解它如何与 Windows + MSVC 一起工作(注意:MinGW 和 Cygwin 以不同的方式实现特定的东西,但实际上回退到 MSVCRT对于大多数事情): http://msdn.microsoft.com/en-us/library/bb985746.aspx

    【讨论】:

    • 在我看来,这个答案是信息最丰富、最准确的一个。太糟糕了,它需要一段时间才能渗透到顶部
    • @S.N.但是,Microsoft 的运行时不只是包装 Kernel32 Heap 函数;它提供了一个 malloc 实现,可以从操作系统分配(我认为来自 VirtualAlloc)中划分出内存。
    • 卡兹,ucrt的malloc正在使用HeapAlloc,来自Kernel32.dll:github.com/huangqinjin/ucrt/blob/master/heap/malloc_base.cpp
    猜你喜欢
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2015-03-01
    • 2014-01-09
    相关资源
    最近更新 更多