c++ - 确保 Windows 正确初始化指向导入函数的函数指针所需的条件是什么?

标签 c++ windows

导入库的实现方式——在here中有描述。 — 令我感到惊讶的是,指向导入函数的函数指针从未被初始化。据说——来自a highly acclaimed article — 这些函数指针以某种方式被视为 IAT 中的一个条目,以便 Windows 及时适本地初始化它们。这些看起来像普通函数指针的函数指针有什么特别之处,使得编译器将它们视为 IAT 中的一个条目?

最佳答案

C++ 编译器可以通过两种方式实现在 C++ 代码中声明的函数指针,这些函数指针使用指向导入函数的指针进行初始化。对于 C++ 编译器,最简单的方法是用导入库中 stub 函数的地址初始化指针。或者,它可以在运行时使用从存储在导入表中的值中获取的函数的实际地址对其进行初始化。

考虑一个用 C++ 编写的 DLL,它导出两个定义如下的函数:

extern "C" void _declspec(dllexport) foo() {
    return;
}

extern "C" void _declspec(dllexport) bar() {
    return;
}

当您编译它并创建一个 DLL 时,链接器还会创建一个导入库。导入库或多或少是一个普通库,而库只是普通对象 (.OBJ) 文件的集合。对于每个导出函数,导入库都定义了 stub 函数和导入指针。 stub 函数通过导入指针调用导入函数。导入指针由操作系统用DLL中导出函数的地址初始化。

在伪代码汇编中,上述示例 DLL 的导入库看起来像这样:

_foo:
      jmp [__imp_foo]

_bar:
      jmp [__imp_bar]

__imp_foo DD ?
__imp_bar DD ?

(导入库中还有其他数据,如 DLL 的名称和导出的函数。)

现在假设您编写了一个使用此 DLL 的 C++ 程序,它包含以下代码:

extern "C" void foo();
extern "C" void _declspec(dllimport) bar();

void (*ptr_to_foo)() = foo;
void (*ptr_to_bar)() = bar;

Microsoft C++ 编译器以不同的方式初始化这两个函数指针。 ptr_to_foo 变量是用 foo 的地址静态初始化的,因为编译器不知道它是从 DLL 导入的。它看起来是一个普通的函数,所以它没有做任何特别的事情。

第二个变量的初始化由微软的编译器以不同的方式实现。因为编译器知道它是一个导入函数,所以它绕过 stub 并直接使用导入指针。由于导入指针的值仅在运行时已知,编译器生成在程序启动时执行的初始化代码(在调用 main 之前)以加载 ptr_to_foo 的值存储在 __imp_foo 中。

当上面的示例程序与我的示例 DLL 的导入库链接时,会创建一个导入 DLL 的可执行文件。这是唯一可以称为神奇的事情发生的地方。由于导入库中导入指针和其他数据的定义方式(特别是在“.idata”部分中定义),链接器知道可执行文件的哪一部分是导入表,并将指向该表的指针存储在可执行文件的 header 。

当操作系统加载可执行文件时,它会看到它有一个导入表,因此会加载其中提到的所有 DLL。然后更新导入表,使可执行文件中的所有导入指针在加载到内存中时指向加载的 DLL 中相应的导出函数。

关于c++ - 确保 Windows 正确初始化指向导入函数的函数指针所需的条件是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29473092/

相关文章:

C++ 内部未声明

c++ - 类范围内的二维 vector

Windows 8 : programmatically preventing start window

java - 在 Java 中从内存中执行一个可执行程序(.exe)

java - 从 Java 调用时隐藏 BAT 文件窗口

C++ 内联汇编错误

c++ - 子串搜索面试题

c++ - 如何有条件地实例化不同的父/子类?

c++ - 是否可以在Windows中存储系统调用的输出?

windows - 如何使用 cmd 行压缩文件?