我正在查看我的 Windows 10 计算机上的 NtDll 导出表,我发现它导出了标准的 C 运行时函数,如memcpy
、sprintf
、strlen
等。
这是否意味着我可以在运行时通过LoadLibrary
and动态调用它们GetProcAddress
?这是否保证适用于每个 Windows 版本?
如果是这样,可以完全删除 C 运行时库(仅使用 NtDll 中的 CRT 函数),从而使我的程序更小?
绝对没有理由调用 NtDll 导出的这些未记录的函数。Windows 将所有基本的 C 运行时函数作为标准系统库(即 Kernel32)的文档化包装器导出。如果您绝对无法链接到 C 运行时库*,那么您应该调用这些函数。对于记忆,你有基本的HeapAlloc
和HeapFree
(或者也许VirtualAlloc
和VirtualFree
) 、、、、、等。对于字符串操作ZeroMemory
,重要的 CRT 函数都在那里,前缀为:、、、、等。奇怪的人是(和它的兄弟FillMemory
MoveMemory
CopyMemory
l
lstrlen
lstrcat
lstrcpy
lstrcmp
wsprintf
wvsprintf
),它不仅具有不同的前缀,而且不支持浮点值(Windows 本身在这些函数首次导出和记录的早期没有浮点代码。)还有各种其他帮助函数也可以复制 CRT 中的功能,例如IsCharLower
、CharLower
、CharLowerBuff
等。
这是一篇旧的知识库文章,其中记录了C 运行时函数的一些 Win32 等效项。如果您重新实现 CRT 的功能,您可能还需要其他相关的 Win32 功能,但这些是直接的、直接的替代品。
其中一些是操作系统的基础架构绝对需要的,并且会被任何 CRT 实现在内部调用。HeapAlloc
此类别包括和之类的东西HeapFree
,它们是操作系统的职责。运行时库仅包装这些内容,提供了一个不错的标准 C 接口和一些其他细节,在具体的操作系统级细节之上。其他的,比如字符串操作函数,只是围绕 CRT 的内部 Windows 版本导出的包装器(除了它是 CRT 的一个非常旧的版本,在历史上的某个时间修复,除了可能已经修补的主要安全漏洞这些年来)。还有一些几乎完全是多余的,或者看起来是这样,就像ZeroMemory
和MoveMemory
, 但实际上是导出的,因此它们可以在没有 C 运行时库的环境中使用,例如经典的 Visual Basic (VB 6)。
有趣的是,许多“简单”的 C 运行时库函数是由 Microsoft(和其他供应商)的编译器作为内部函数实现的,并进行了特殊处理。这意味着它们可以进行高度优化。基本上,相关的目标代码是直接在应用程序的二进制文件中直接发出的,从而避免了可能需要昂贵的函数调用。允许编译器为类似的东西生成内联代码strlen
,并且一直被调用,这几乎无疑会带来更好的性能,而不是支付对导出的 Windows API 之一的函数调用的成本。编译器无法“内联”lstrlen
; 它像任何其他函数一样被调用。这让您回到速度和大小之间的经典权衡。有时较小的二进制文件更快,但有时不是。不必链接 CRT 将生成更小的二进制文件,因为它使用函数调用而不是内联实现,但在一般情况下可能不会生成更快的代码。
*但是,出于各种原因,您确实应该链接到与编译器捆绑在一起的 C 运行时库,其中最重要的是可以通过运行时库的更新版本分发到所有操作系统版本的安全更新. 你必须有一个非常好的理由不要使用 CRT,例如,如果您正在尝试构建世界上最小的可执行文件。没有这些功能将只是你的第一个障碍。CRT 为您处理了很多您通常不需要考虑的事情,例如启动和运行进程、设置标准 C 或 C++ 环境、解析命令行参数、运行静态初始化程序、实现构造函数和析构函数(如果您正在编写 C++),支持结构化异常处理(SEH,也用于 C++ 异常)等等。我已经获得了一个简单的 C 应用程序,可以在不依赖 CRT 的情况下进行编译,但这需要花费很多时间,而且我当然不会推荐它用于任何严重的事情。马修威尔逊很久以前写了一篇关于避免使用 Visual C++ 运行时库。它在很大程度上已经过时了,因为它专注于 Visual C++ 6 开发环境,但是很多大局的东西仍然是相关的。很久以前,Matt Pietrek 在 Microsoft Journal 上也写了一篇关于此的文章。标题是“幕后:使用 LIBCTINY.LIB 减少 EXE 和 DLL 大小”。仍然可以在 MSDN上找到副本,如果在 Microsoft 的一次重组期间无法访问,可以在 Wayback Machine 上找到。(向IInspectable和Gertjan Brouwer致敬以挖掘链接!)
如果您只是需要将 C 运行时库 DLL 与您的应用程序一起分发,您可以考虑静态链接到 CRT。这会将代码嵌入到您的可执行文件中,并消除了对单独 DLL 的要求。同样,这会使您的可执行文件膨胀,但确实使部署变得更简单,而无需安装程序甚至 ZIP 文件。当然,最大的警告是您无法从 CRT DLL 的增量安全更新中受益。您必须重新编译和重新分发应用程序才能获得这些修复。对于没有其他依赖的玩具应用,我经常选择静态链接;否则,动态链接仍然是推荐的方案。
NtDll 中有一些 C 运行时函数。根据Windows Internals,这些仅限于字符串操作函数。还有其他等价物,例如使用 HeapAlloc 代替 malloc,因此您可以根据自己的要求侥幸逃脱。
尽管这些函数已被 Microsoft 出版物承认并已被内核程序员使用多年,但它们不是官方 Windows API 的一部分,您不应将它们用于玩具或演示程序以外的任何东西,因为它们的存在和功能可能改变。
您可能想在此处阅读有关为 Rust 语言执行此操作的选项的讨论。
这是否意味着我可以在运行时通过 LoadLibrary 和 GetProcAddress 动态调用它们?
是的。甚至更多 - 为什么不使用 ntdll.lib(或 ntdllp.lib)静态绑定到 ntdll ?在此之后,您可以直接调用此函数而无需任何 GetProcAddress
这是否保证适用于每个 Windows 版本?
从nt4到win10,在ntdll中存在很多C运行时函数,但设置不同。通常它会从一个版本到另一个版本。但是一些功能比较弱的 msvcrt.dll 。例如printf
从 ntdll 说不支持浮点格式,但一般功能是相同的
可以完全删除 C 运行时库(仅使用 NtDll 中的 CRT 函数),从而使我的程序更小?
是的,这是 100% 可能的。