17

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

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

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

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

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

如果您对这些东西很了解,请也看看我的另一个问题。谢谢!

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

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

3 回答 3

8

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

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

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

于 2009-10-17T21:11:27.497 回答
7

用 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++ newdelete和 static 对象以及非零出所有成员的构造函数
  • 使用标准库函数,如fopen()printf()当然

疑难解答

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

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

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

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

于 2012-10-10T13:21:08.223 回答
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 开始,则需要编译器 hack(注意:GCC 在某些情况下会这样做)在 main 的开头插入函数调用。相反,在大多数操作系统上,在大多数情况下,一个不同的函数构造 g_class 然后调用 main(在 Windows 上,这是 mainCRTStartup 或 WinMainCRTStartup;在大多数其他操作系统上,我已经习惯了它是一个名为 _start 的函数)。

在 main 工作之前或之后,还有 C++ 甚至 C 需要完成的其他事情。一旦 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

于 2014-03-31T03:33:18.160 回答