_WinMainCRTStartup函数有哪些功能?

17

这是至少两个密切相关但不同的问题系列的一部分。我希望通过分开提问它们,能做对的事情。

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

因此,我将 项目属性 -> 配置 -> C/C++ -> 高级 -> 省略默认库名称 设置为 是(编译器标志 /Zl),并重新构建。

然后,链接器抱怨未解析外部符号_WinMainCRTStartup。可以理解,我可以告诉链接器使用另一个入口点,比如MyStartup。从我在网上收集到的信息来看,_WinMainCRTStartup执行了一些初始化操作,我可能希望MyStartup执行其中的一部分。

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

如果您对此有所了解,请再看一下我的另一个问题。谢谢!

附:我为什么要这样做?

  1. 我的应用程序没有明确使用任何CRT函数。
  2. 我喜欢精简的应用程序。
  3. 这会教会我一些新东西。
3个回答

8

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

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

Visual Studio提供了CRT源代码,您可以在调试器中逐步跟踪CRT的入口点,并找出它确切地在做什么。


谢谢,我不知道源代码是开放的。看起来我的问题已经过时了。你确定C++构造函数的事情吗?我似乎找不到它。(不过这也没什么关系;我猜这只是针对全局变量的?) - Thomas
C++构造函数的事情在__initterm里面,我相信是这样的。这是我凭记忆说的,可能有些错误,但是C++构造函数会在某个数据段中注册,并且__initterm会遍历它。 - Michael

7
用C编写的真正的Win32程序(不是C++),根本不需要任何初始化,因此您可以使用WinMainCRTStartup()而不是WinMain(HINSTANCE,...)来启动您的项目。
编写控制台程序作为真正的Win32应用程序也是可能的,但有点困难;入口点的默认名称为_mainCRTStartup()
禁用所有额外的代码生成功能,例如堆栈探测、数组检查等。仍然可以进行调试。
有时需要第一个HINSTANCE参数。对于Win32(除了Win32s),它固定为(HINSTANCE)0x400000nCmdShow参数始终为SW_SHOWDEFAULT
如果需要,使用GetCommandLine()检索命令行。
当您的程序生成线程时,例如通过调用GetOpenFileName(),从WinMainCRTStartup()返回关键字return将使您的程序挂起——请改用ExitProcess()
当您遇到以下情况时,您会遇到相当大的麻烦:
- 使用堆栈帧(即局部变量)大于4 KBytes(每个函数) - 使用浮点运算(例如float->int转换) - 在32位机器上使用64位整数(乘法、位移操作) - 使用具有非零-out-all-members构造函数的C++newdelete和静态对象 - 使用标准库函数,如fopen()printf()
在所有Windows系统上都有一个C标准库可用(自Windows 95以来),即MSVCRT.DLL
要使用它,请导入它们的入口点,例如使用我的msvcrt-light.lib(Google搜索)。但是仍然存在一些注意事项,特别是在使用比MSVC6更新的编译器时:
- 堆栈帧仍然限制为4 KBytes - _ftol_sse_ftol2_sse必须路由到_ftol - _iob_func必须路由到_iob 它的初始化似乎在加载时运行。至少文件功能将无缝运行。

使用C++的new、delete和静态对象,以及非零初始化构造函数的成员。 - user90843
4
对于Win32(除了Win32s),它被固定为(HINSTANCE)0x400000。- 这是不正确的。 - Xearinox
这个问题非常老了,但这些信息将会无限帮助我。你如何将 _ftol_sse 和 _ftol2_sse 路由到 _ftol?我一直在尝试将浮点数转换为整数时遇到这个问题,最终我创建了一个 sse2 函数来完成它,然后将结果转换为 int。它似乎避免了调用 _ftol2_sse,但这不是我想要的方式。 - applecider
@Xearinox:没错。实际上,模块的HINSTANCE只是指向模块基地址(/BASE链接器参数)的指针;提到的地址只是可执行文件的默认地址,假设没有ASLR发生。 - alecov

6

这是一个老问题,但现有的答案要么不正确,要么只关注某个具体问题。

如果程序真的在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();
}

为了使这个程序按预期工作,必须在main函数之前调用my_class的构造函数。如果程序恰好从main开始,则需要编译器黑客(注意: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运行时实现其自己的堆栈(Microsoft的C运行时只是包装Kernel32 Heap函数)。
甚至传递给main的参数argc和argv也必须从系统中获取。
您可能想查看Matt Pietrick(古老的)有关在Windows + MSVC上实现自己的C运行时的具体信息的文章(注意:MinGW和Cygwin以不同的方式实现特定事物,但实际上大多数都回退到MSVCRT): http://msdn.microsoft.com/en-us/library/bb985746.aspx

这个答案在我看来是最具信息量和准确性的。可惜它需要一段时间才能浮现到顶部。 - Motorhead
@S.N. 然而,微软的运行时不仅仅包装了 Kernel32 堆函数;它还提供了一个 malloc 实现,从操作系统分配(我想是从 VirtualAlloc)中切割出内存。 - Kaz
Kaz,ucrt的malloc使用了Kernel32.dll中的HeapAlloc:https://github.com/huangqinjin/ucrt/blob/master/heap/malloc_base.cpp - nfries88

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接