0

尽管手头的问题已经解决,但对于使用什么数据来构造类的 vtable 以及 vtable 的布局存储在哪里,我还是有点困惑。如果有人可以提供澄清或指出一些可能满足我好奇心的信息,我将不胜感激。

背景

  1. 两个独立的 VC6.0 项目:一个用于 exe,一个用于 dll。
  2. 应用程序包含来自 dll 项目版本的 .lib、.dll 和 .h 文件。
  3. 当新版本准备就绪时,.lib、.dll 和 .h 文件将从 dll 项目复制到 exe 项目中。
  4. 我继承了这个方案。

问题:

我最近对 ​​DLL 进行了更改并复制了 .lib 和 .dll 但忘记复制头文件。层次结构发生了一些变化,因此,一个特定类(称为它InternalClass)的 vtable 发生了变化。

exe 不会直接调用InternalClass. 相反,它创建了一个不同类的实例(称为它InterfaceClass),它封装了一个指向InternalClass对象的指针并针对该指针调用各种方法。

在运行时,从InterfaceClass方法内部对InternalClass方法的调用实际上是在调用错误的方法(即InterfaceClass会调用InternalClass::AInternalClass::B实际运行)。查看asm,事实证明 fixup 或 thunk(如果这是错误的行话,我很抱歉!)正在使用正确的偏移量到 vtable 中。但是,vtable 本身包含您希望从头文件中获得的指针列表。

实际问题:

我意识到我的错误,将头文件复制过来,重新编译,一切都很好。然而,我留下了一个挥之不去的问题:

何时确定 vtable 的布局以及在运行时使用哪些信息为这些类构建 vtable?也就是说,在我看来,在编译 dll 时必须已经组装了正确的vtable,以便偏移量等可以用于从InterfaceClassto的调用InternalClass。那么,为什么在运行时使用过时的 vtable 呢?使用它拥有的头文件编译exe时,布局是否单独确定?

我不确定这是否完全清楚。让我知道我的要求是否过于复杂。谢谢!

4

2 回答 2

2

嗯,一个 12 岁的编译器的重型实现细节。在类上使用 __declspec(dllexport) 时,链接器会导出该类的成员。不是 vtable。当编译器解析头文件中的类声明时,它会在类的客户端中重建。链接器用导出的成员地址填充该本地 vtable。

如果编译器只是从 DLL 中导出 vtable 会更好吗?也许吧,但是导出这样的实现细节是非常脆弱的。我想,害怕把自己画到一个由于向后兼容性而无法摆脱的角落是一个强大的动力。

于 2010-08-10T21:35:59.573 回答
1

您描述这两个类的交互的方式在一个非常重要的细节上并不完全清楚:您所谓InternalClass的是否从 dll 中导出?或者以不同的方式提出问题:那些复制的头文件中的代码是否使用指向该类的指针,InternalClass或者该类的所有知识是否仅隐藏在 dll 中?

如果该类实际上完全隐藏在 dll 中,那么我不确定这个问题是如何发生的。但是从您的描述来看,听起来确实像是InternalClass被dll暴露了。

如果那些被复制的标头包含与此概念类似的代码:

class InterfaceClass
{
    InternalClass* Internal;

    void DoSomething()
    {
        Internal->DoSomething();
    }
};

然后InternalClass必须暴露,否则exe的项目将无法编译头文件。(所以它毕竟不是内部的。)

如果是这样,那么它很容易解释你的问题。exe 是根据与InternalClass构建 dll 的声明不同的声明构建的。这意味着每个人都编译自己的虚拟表版本,并且这些版本是不同的。

然后在运行时,exe 代码被交给一个从 dll 中创建的对象,因此它的 vtable 指针指向 dll 的 vtable 版本。然后,exe 代码尝试通过该 vtable 指针工作,就好像它指向 exe 的 vtable 版本一样。显然这会导致问题。

于 2010-08-10T22:11:06.277 回答