关于C#:已加载共享库中的类冲突

Class conflicts in loaded shared libraries

说我的主要过程有一个课:

1
2
3
4
5
6
7
class MyClass
{
    void doStuff();
    int myThing;
    int mySecondThing;
    bool myThirdThing;
};

然后我加载一个共享库mysharedlib.so,其中包含使用以下版本编译的较新版本的类:

1
2
3
4
5
6
7
8
9
class MyClass
{
    void doStuff();
    int myThing;
    int mySecondThing;
    bool myThirdThing;
    std::string myFourthThing;
    u32 myFifthThing;
};

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

我知道这两个库位于不同的地址空间中,但是让我在库和可执行文件之间传递数据使我感到困惑。

使用gmodule时此行为是否有所不同?


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

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

采取以下情况(此处为伪代码)。

1
2
3
4
5
6
7
8
9
10
11
12
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您将更改错误的参数。具有讽刺意味的是,如果您继续在可执行文件中使用该类并将其不传递回.so(让我们说无知是一种幸福),那将无效。

  • 访问未分配的成员
  • 当您的可执行文件分配localAllocPtr时,它将以您的可执行文件中定义的sizeof(MyClass)进行分配。在可执行文件中,该类未定义字符串和u32。当将此分配的结构传递给.so时,.so将根据其定义将类视为具有成员和大小。访问myFourthThing时,它将访问通常为*(ptr 8)的内存区域。如果正在使用该内存区域(有人在那儿分配了内存),您将超出ptr范围写入其他人的内存,这似乎可以正常工作,但最终会发现最困难的错误之一。如果在*(ptr 8)之后未分配任何内容,则您会很幸运并遇到细分错误。

    为了避免描述这种问题,您要描述的一种通用方法是pImpl习惯用法,它可以使特定于类的实现私有化,因此您可以添加虚拟函数和成员变量,同时保持暴露的类定义一样。