假设我的主要流程中有一个班级:
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
?
答案 0 :(得分:0)
使用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处理这样的场景,其中类被实例化,因此使用在那里定义的大小,然后由具有在那里定义的大小的可执行文件使用。
localAllocPtr处理反向场景(在您的可执行文件中实例化的类,然后传递给.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习惯用法,它允许你将类特定的实现设为私有,这样你就可以添加虚函数和成员变量,同时保持类的公开定义同样的。