Class 加载的共享库中存在冲突

Class conflicts in loaded shared libraries

假设我的主进程中有一个 class:

class MyClass
{
    void doStuff();
    int myThing;
    int mySecondThing;
    bool myThirdThing; 
};

然后我加载了一个共享库,mysharedlib.so,其中 更新 版本的 class 编译于:

class MyClass
{
    void doStuff();
    int myThing;
    int mySecondThing;
    bool myThirdThing;
    std::string myFourthThing;
    u32 myFifthThing; 
};

当我创建 MyClass 实例/在库函数和主要可执行文件函数之间传递现有实例时会发生什么?

我知道 the two libraries live in a different address space 但是在库和可执行文件之间传递数据让我很困惑。

使用 gmodule 时,这是否有不同的表现?

使用 MyClass 对象时可能会出现问题。

这取决于你如何使用它们。

采用以下场景(此处为伪造代码)。

MyClass* ptr = SharedLibHandle->CreateMyClass();
ptr->doStuffNonVirtual(); //1 this might work fine
ptr->doStuffVirtual(); //2 this will work fine
ptr->myThing= 5; // 3 this might work fine

MyClass* localAllocPtr = new MyClass();
SharedLibHandle()->DoSomethingWithTheClass(localAllocPtr);
...
void DoSomethingWithTheClass(MyClass* ptr)
{
 ptr->myFourthThing = " mama " ; // 4 this might seem to work fine
}

在上面的示例中,根据实例化和使用的位置,有几种可能的用例:

ptr 处理 class 在 so 中以 so 中定义的大小实例化,然后由您的可执行文件以此处定义的大小使用的场景。

localAllocPtr 处理相反的情况(class 在您的可执行文件中实例化然后传递给 .so)。

各拿一张:

  1. 调用非虚函数。

非虚函数在编译时解析,这意味着如果您的可执行文件中有不同的代码实现,堆栈指针将跳转到您的函数实现而不是 .so 中的函数实现。如果您的代码在可执行文件和 so 中都相同,并且结构对齐方式保持不变(这很可能),它将按预期工作。

  1. 调用虚函数

这会很好地工作,因为它会跳转到一个 vftable,然后跳转到 .so 中的正确内存地址。 .so 初始化了 class,因此偏移、跳转和一切都是合法的。

  1. 访问共同定义的成员

只有当 myThing 在结构内具有相同的对齐方式时,这才能正常工作,这意味着他在结构内的 *(ptr+0) 偏移处。如果在您的 class 中 myThing 是第一个而 mySecondThing 是第二个,而在 .so 中 mySecondThing 是第一个而 myThing 是第二个,那么您将更改错误的参数。具有讽刺意味的是,如果您继续在可执行文件中使用 class 并且不将其传回 .so(假设无知是一种幸福)。

  1. 访问未分配的成员

当您的可执行文件分配 localAllocPtr 时,它将使用您的可执行文件中定义的 sizeof(MyClass) 分配它。在您的可执行文件中,class 没有定义字符串和 u32。将此分配的结构传递给 .so 时,.so 将根据其定义将 class 视为具有成员和大小。当访问 myFourthThing 时,它将访问通常为 *(ptr + 8) 的内存区域。如果该内存区域正在使用中(有人分配在那里),您将在您的 ptr 范围之外写入其他人的内存,并且它可能看起来工作正常但您最终会遇到最难找到的错误之一。如果在 *(ptr +8) 之后什么都没有分配,你会很幸运并得到一个段错误。

为了避免您所描述的那种问题,一种常见的方法是 pImpl idiom ,它允许您将 class 特定实现设为私有,因此您可以添加虚函数和成员变量,同时保持class 相同的暴露定义。