执行 Win API 的实际机器代码保留在 OS 内核内存 space 中还是作为应用程序的一部分一起编译?

actual machine code to execute what Win APIs do stays in OS kernel memory space or compiled together as part of the app?

如果这个问题涉及到太基础的问题,请见谅。

作为一个somewhat-close-to-beginner-level程序员,我真的很想知道这个——每个winAPI函数的底层代码是在编写应用程序的时候一起编译的,还是机器码用于执行 win APIs 作为 OS 的一部分保留在内存中,因为 pc 已启动,只有应用程序使用它们?

一个OS的所有API都被许多应用程序通过函数调用的方式使用。所以我认为与其让每个单独的应用程序自己包含 API 机器代码,不如让应用程序只包含 header 或签名来调用 APIs 和 API启动应用程序时映射机器代码地址。

很抱歉,由于我的英语不好,我没能把这个问题简明扼要。我真的很想得到你的见解。谢谢。

(大多数)API 调用的实现由系统通过编译模块(Portable Executable 图像)提供。应用程序代码只包含足够的信息,以便系统可以识别和加载所需的模块,并解析相应的导入。

例如,考虑以下显示消息框的代码,等待它关闭,然后退出程序:

#include <Windows.h>

int main()
{
    ::MessageBoxW(nullptr, L"Foo", L"Bar", MB_OK);
}

给定函数签名(在 WinUser.h 中声明,从 Windows.h 中提取)编译器可以几乎 生成call 指令。它知道参数的数量、它们的预期类型以及被调用者期望它们的顺序和位置。缺少的是 user32.dll 中的实际目标地址,只有在之后才知道进程已完全初始化,并将 user32.dll 模块映射到其地址 space.

显然,编译器无法将代码生成推迟到加载时间之后。它需要生成 call 指令 now。因为我们知道 "all problems in computer science can be solved by another level of indirection" 这也是编译器所做的:它不是发出直接调用指令,而是生成间接调用。不同之处在于,直接调用需要立即提供目标地址,而间接调用可以指定存储目标地址的地址。

在x86汇编中,不用说

    call _MessageBoxW@16   ; uh-oh, not yet known

编译器可以方便地将调用委托给 Import Address Table (IAT):

    call dword ptr [__imp__MessageBoxW@16]

避免了灾难,我们已经为我们赢得了足够的时间来在代码实际执行之前解决问题。

创建进程对象后,系统将控制权移交给其主线程以完成初始化。该初始化的一部分是加载依赖项(例如此处的 user32.dll)。完成后,系统最终知道加载地址(最终是导入符号的地址,例如 _MessageBoxW@16),并可以用导入的函数地址覆盖地址 __imp__MessageBoxW@16 处的 IAT 条目。

这大致就是系统如何提供系统服务的实现,而不需要客户端应用程序知道它们将在何处(物理上)找到它们。

我说的是“大约”,因为事情更多地涉及到现实。如果那是你想了解的东西,我会把它留给 Raymond Chen。他发布了 series 篇更详细地涵盖该主题的博客文章: