NtDll 是否真的导出 C 运行时函数,我可以在我的应用程序中使用这些函数吗?

Does NtDll really export C runtime functions, and can I use these in my application?

我在 Windows 10 计算机上查看 NtDll 导出 table,我发现它导出标准 C 运行时函数,如 memcpysprintf , strlen, 等等

这是否意味着我可以在运行时通过 LoadLibraryGetProcAddress 动态调用它们?是否保证每个 Windows 版本都是如此?

如果是这样,是否可以完全删除 C 运行时库(仅使用 NtDll 中的 CRT 函数),从而使我的程序更小?

NtDll 中有一些 C 运行时函数。根据 Windows Internals,这些仅限于字符串操作函数。还有其他等价物,例如使用 HeapAlloc 而不是 malloc,因此您可以根据自己的要求摆脱它。

虽然这些函数得到了 Microsoft 出版物的认可,并且内核程序员已经使用了很多年,但它们并不是官方 Windows API 的一部分,您不应该将它们用于玩具或演示程序以外的任何东西,因为它们的存在和功能可能会改变。

您可能想阅读有关为 Rust 语言执行此操作的选项的讨论 here

绝对没有理由调用这些由 NtDll 导出的未记录的函数。 Windows 将所有基本的 C 运行时函数导出为标准系统库(即 Kernel32)中记录的包装器。如果您 绝对 不能 link 到 C 运行时库 *,那么您应该调用这些函数。对于记忆,你有基本的HeapAllocHeapFree(或者可能是VirtualAllocVirtualFree),ZeroMemoryFillMemoryMoveMemory, CopyMemory, 等等。对于字符串操作,重要的 CRT 函数都在那里,以 l 为前缀:lstrlen, lstrcat, lstrcpy, lstrcmp,等等。奇怪的是wsprintf(和它的兄弟wvsprintf),它不仅有不同的前缀,而且不支持浮点值(Windows 在早期首次导出和记录这些函数时本身没有浮点代码。)还有各种其他辅助函数,它们复制了 CRT 中的功能,例如 IsCharLowerCharLowerCharLowerBuff、等等

这是一篇旧的知识库文章,其中记录了一些 Win32 Equivalents for C Run-Time Functions。如果您重新实现 CRT 的功能,可能还需要其他相关的 Win32 函数,但这些是直接的、直接的替代品。

其中一些是操作系统基础结构绝对需要的,并且会被任何 CRT 实现在内部调用。此类别包括 HeapAllocHeapFree 之类的内容,它们由操作系统负责。一个运行时库只包装那些,提供一个很好的标准 C 接口和一些其他细节之上的细节 OS 级细节。其他的,比如字符串操作函数,只是围绕内部 Windows 版本的 CRT 导出的包装器(除了它是一个非常旧的 CRT 版本,在历史上的某个时间修复,除了可能存在的主要安全漏洞多年来已经修补)。还有一些几乎完全多余,或者看起来完全多余,例如 ZeroMemoryMoveMemory,但实际上是导出的,以便可以在 的环境中使用它们C 运行时库,类似于经典的 Visual Basic (VB 6)。

还需要指出的是,许多“简单的”C 运行时库函数都是由 Microsoft(和其他供应商的)编译器实现的 intrinsic functions,并进行了特殊处理。这意味着它们可以被高度优化。基本上,相关的目标代码直接在您的应用程序的二进制文件中以内联方式发出,避免了潜在的昂贵函数调用的需要。允许编译器为像 strlen 这样一直被调用的东西生成内联代码,这几乎无疑会带来更好的性能,而不是必须为导出的 Windows 之一支付函数调用的成本蜜蜂。编译器没有办法“内联”lstrlen;它像任何其他函数一样被调用。这让您回到速度和大小之间的经典权衡。有时较小的二进制文件更快,但有时不是。不必 link CRT 将生成更小的二进制文件,因为它使用函数调用而不是内联实现,但在一般情况下可能不会生成更快的代码。

* 但是,您 确实 应该 link 使用与您的编译器捆绑在一起的 C 运行时库,因为多种原因,其中最重要的是可以通过运行时库的更新版本分发到所有操作系统版本的安全更新。您必须有一个 非常好的理由 不使用 CRT,例如如果您正在尝试构建世界上最小的可执行文件。不提供这些功能只是您遇到的第一个障碍。 CRT 为您处理很多您通常不需要考虑的事情,例如启动进程和 运行、设置标准 C 或 C++ 环境、解析命令行参数、运行 静态初始值设定项,实现构造函数和析构函数(如果您正在编写 C++),支持结构化异常处理(SEH,也用于 C++ 异常)等等。我已经得到了一个简单的 C 应用程序来编译而不依赖于 CRT,但是它花了很多时间,我当然不会推荐它用于任何远程严肃的事情。 Matthew Wilson 在 很久 前写了一篇关于 Avoiding the Visual C++ Runtime Library. It is largely out of date, because it focuses on the Visual C++ 6 development environment, but a lot of the big picture stuff is still relevant. Matt Pietrek wrote an article about this in the Microsoft Journal a long while ago, too. The title was "Under the Hood: Reduce EXE and DLL Size with LIBCTINY.LIB". A copy can still be found on MSDN and, in case that becomes inaccessible during one of Microsoft's reorganizations, on the Wayback Machine. (Hat tip to IInspectable and Gertjan Brouwer 的文章,用于挖掘 links!)

如果您只关心与应用程序一起分发 C 运行时库 DLL 的需要,您可以考虑静态 linking 到 CRT。这会将代码嵌入到您的可执行文件中,并消除了对单独 DLL 的需求。同样,这会使您的可执行文件膨胀,但确实可以简化部署,而无需安装程序甚至 ZIP 文件。当然,最大的警告是您无法从 CRT DLL 的增量安全更新中获益;您必须重新编译并重新分发应用程序才能获得这些修复。对于没有其他依赖的玩具应用,我经常选择静态link;否则,动态 linking 仍然是推荐的方案。

Does that mean that I can call them dynamically at runtime through LoadLibrary and GetProcAddress?

是的。甚至更多 - 为什么不使用 ntdll.lib(或 ntdllp.lib)静态绑定到 ntdll?之后你可以直接调用这个函数而不需要任何 GetProcAddress

Is this guaranteed to be the case for every Windows version?

从nt4到win10,ntdll中都有很多C运行时函数,只是设置不同而已。通常它从一个版本增长到另一个版本。但有些功能较差 msvcrt.dll 。例如来自 ntdll 的 printf 不支持浮点格式,但一般功能是相同的

it is possible to drop the C runtime library altogether (by just using the CRT functions from NtDll), therefore making my program smaller?

是的,这是 100% 的可能性。