假设我的主进程中有一个类:
class MyClass
{
void doStuff();
int myThing;
int mySecondThing;
bool myThirdThing;
};
然后我加载了一个共享库,mysharedlib.so
,其中编译了类的更新版本:
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 处理类在 so 中以 so 中定义的大小实例化,然后由您的可执行文件以此处定义的大小使用的场景。
localAllocPtr 处理相反的情况(类在您的可执行文件中实例化,然后传递给 .so)。
每一个:
- 调用非虚函数。
非虚函数在编译时解析,这意味着如果您的可执行文件中有不同的代码实现,堆栈指针将跳转到您的函数实现而不是 .so 中的实现。如果您的代码在可执行文件和 so 中都相同,并且结构对齐方式保持不变(这很可能),它将按预期工作。
- 调用虚函数
这会很好地工作,因为它会跳转到一个 vftable,然后跳转到 .so 中的正确内存地址。 .so 初始化了类,因此偏移、跳转和一切都是合法的。
- 访问共同定义的成员
只有当 myThing 在结构内具有相同的对齐方式时,这才能正常工作,这意味着他在结构内的 *(ptr+0) 偏移处。如果在你的类中有任何机会 myThing 是第一个而 mySecondThing 是第二个,而在 .so 中 mySecondThing 是第一个而 myThing 是第二个,那么你将更改错误的参数。具有讽刺意味的是,如果您继续在可执行文件中使用该类而不将其传回 .so(假设无知是一种幸福),这将没有任何效果。
- 访问非分配成员
当您的可执行文件分配 localAllocPtr 时,它将使用您的可执行文件中定义的 sizeof(MyClass) 分配它。在您的可执行文件中,该类没有定义字符串和 u32。将此分配的结构传递给 .so 时,.so 将根据其定义将类视为具有成员和大小。当访问 myFourthThing 时,它将访问通常为 *(ptr + 8) 的内存区域。如果该内存区域正在使用中(有人分配在那里),您将在您的 ptr 范围之外写入其他人的内存,并且它可能看起来工作正常但您最终会遇到最难找到的错误之一。如果在 *(ptr +8) 之后什么都没有分配,你会很幸运并得到一个段错误。
为了避免您所描述的那种问题,一种常见的方法是 pImpl idiom ,它允许您将特定于类的实现设为私有(private),这样您就可以添加虚函数和成员变量,同时保持类的公开定义一样。
关于c++ - 加载的共享库中的类冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28185502/