2

Say I have a class in my main process:

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

And I load a shared library, mysharedlib.so, with a newer version of the class compiled in:

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

What happens when I create an instance of MyClass / pass existing instances around between the library's functions and the main executable's functions?

I know the two libraries live in a different address space but passing the data between the library and the executable is what confuses me.

Does this behave differently when using gmodule?

Community
  • 1
  • 1
Christian Stewart
  • 15,217
  • 20
  • 82
  • 139
  • 1
    Have you given it a try? Intuitively, if your `MyClass` instance is allocated in `main`, you will get a segfault/bad things will happen as soon as `mysharedlibrary.so` tries to access any of the new members. If the objects are encoded equally except for the two new members at the end (a big if, probably compiler dependent?), you *may just about* get away with it if the instance is allocated in `mysharedlib.so` and all you get in `main` is a pointer. This is purely based on my intuition. – Henrik Jan 28 '15 at 07:08
  • http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html .. and .. http://www.linuxjournal.com/article/3687?page=0,0 .. give a little more insight to the matter, but @Henrik is right in you "might get away with it" – txtechhelp Jan 28 '15 at 07:41

1 Answers1

0

Problems might occur when using MyClass objects .

It depends on how you use them.

Take the following scenario (bogus code here).

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
}

In the example above there are several possible use cases based on the place of instantiation and usage :

ptr handles the scenario where the class is instantiated in the so with the size defined in the so , then used by your executable with the size defined there.

localAllocPtr handles the reverse scenario (class instantiated in your executable then passed to .so).

Taking each one :

  1. Call to non virtual function.

Non virtual functions are resolved at compile time, this meaning that if you have a different code implementation inside your executable, the stack pointer will jump to your function implementation instead of the one in the .so . It will work as expected if your code is the same in both of the executable and so , and the structure alignment remains the same (which is most likely).

  1. Call to virtual function

This will work fine, since it will jump into a vftable then jump to the correct memory address in the .so . The .so initialized the class, so offsets, jumps and everything will be legal.

  1. Access of commonly defined member

This will work fine only if myThing has the same alignment inside the structure, meaning he's at *(ptr+0) offset inside the structure. If by any chance inside your class myThing is first and mySecondThing is second, while in the .so mySecondThing is first while myThing is second, then you will change the wrong parameter. Which ironically will have no effect if you continue to use the class inside your executable and not pass it back to the .so (Let's say ignorance is a bliss).

  1. Access to a non-allocated member

When your executable allocs localAllocPtr it will allocate it with the sizeof(MyClass) as defined in your executable. In your executable the class doesn't define a string and a u32. When passing this allocated structure to the .so , the .so will consider the class as having the members and size according to it's definition. When accessing myFourthThing it will access a zone of memory that would normally be *(ptr + 8). If that zone of memory is in use (someone allocated there) you will write outside your ptr bounds into someone else's memory, and it might seem to work fine but you will end up with one of the hardest bugs to find. If after *(ptr +8) nothing is allocated, you'll get lucky and get a segmentation fault.

In order to avoid the sort of problem you are describing a common approach is the pImpl idiom , which allows you to make the class specific implementation private, so you can add virtual functions and member variables while keeping the exposed definition of the class the same.

MichaelCMS
  • 4,703
  • 2
  • 23
  • 29
  • Thank you. I was trying to decide if I can transfer parsed protobuf objects between my libraries and the main or if I need to reparse the binary array a second time. – Christian Stewart Jan 28 '15 at 17:20