Shared objects in Linux

Started by
8 comments, last by Dospro 15 years, 3 months ago
Hi everyone. I have aproblem, i don't know how to solve. I have an abstract class, yea pure virtual, which serves as an interface. Then in a shared object i made an implementation of the base class. With another class i made all the loading things. Everything compiles fine. But when i run it, it says something like:
Quote: ERROR: Couldn't load DPI_Render/DPI_render_OGL/libdpiogl.so DPI_Render/DPI_render_OGL/libdpiogl.so: undefined symbol: _ZN11cDPI_RenderD2Ev
where cDPI_Render is the name of my abstract class. Here are the relevant portions of my code.

//This the base class
#ifndef DPI_RENDER_DEVICE
#define DPI_RENDER_DEVICE


class cDPI_Render {
	public:
		cDPI_Render(){};
		virtual ~cDPI_Render(){};
		
		virtual bool Init(int width, int height)=0;
		virtual void ClearScreen(float r, float g, float b)=0;
		virtual void Update(void)=0;
		virtual void Release(void)=0;
};

extern "C" {
	bool createRenderDevice(cDPI_Render *dpird_ptr);
	void releaseRenderDevice(cDPI_Render *dpird_ptr);
};

#endif

I used extern "C" so the names don't get screwed up int the shared object so i could get them easily with dlsym(...) This is the code that fails:

bool cSOLoader::create(int flag, char *shName)
{
	bool (*render_ptr)(cDPI_Render *dpir_ptr);
	bool (*input_ptr)(cDPI_Input *dpii_ptr);
	bool hres;
	
	switch(flag)
	{
		case DPI_RENDER:
			sh_module[DPI_RENDER]=dlopen(shName, RTLD_NOW|RTLD_GLOBAL);/*Here it fails*/

.
.
.
.

And finally, the test program which shows how i load the library.

#include"DPI_Render/dpi_render.h"
#include<stdio.h>


int main(int argc, char *argv[])
{
	cDPI_Render *render;
	cSOLoader loader;
	printf("Iniciando\n");
	
	loader.create(DPI_RENDER, "DPI_Render/DPI_render_OGL/libdpiogl.so");
	puts("Loader creado");
	render=(cDPI_Render*)loader.getObject(DPI_RENDER);
	puts("Render creado");
	loader.release();
	puts("Adios");
	
	
	return 0;
}

Somehow the name of the abstract class gets screwed when compiling the shared object. Any ideas how to fix this?
Advertisement
extern "C" only works for functions. Classes related symbols are always going to get mangled to encode more informations in them than just the name.

There a small command line utility that comes with binutils (and so should be available if gcc is) called c++filt that can decode a mangled c++ symbol name.

So, with that said, if you run _ZN11cDPI_RenderD2Ev through c++filt, you'll see that it is cDPI_Render::~cDPI_Render().
What probably happens is that the virtual function table for cDPI_Render in libdpiogl.so references cDPI_Render's destructor, but the actual non-inline version of the destructor hasn't been generated because it's declared inline.

A virtual function is always going to be called through the virtual function table though, so declaring them inline is pointless and makes little sense.

So my suggestion is: try to put cDPI_Render's destructor body in a cpp file instead of directly in the header file.
Well. Testing what you said, first i commented the constructor and the destructor and it worked fine(although with possibly memory leaks). The i uncommented both, but delete the body of both.
I'm still getting the same problem.
Do i really have to create a cpp just to solve this problem? i mean, neither the constructor nor the destructor actually have any code, so, what other way, can this be solved?

EDIT:

Alright. i don't know why, but this time the code worked. I left just as i posted it above and works. ??????????
Any ideas, what's going on?
The root of your problem is that there was no implementation of the destructor in the runtime linkloader's search path at the time it was needed.

If it didn't used to work but now works, I would suspect some procedural problem in which you are picking up older libraries at run time, and after some futzing around the newer libraries get put in place.

There is also the problem of where the class-specific code gets emitted: it's pretty arbitrary but you can force it with GCC by providing a non-inline destructor in a separate .cc file. That would eliminate the possibility that the compiler or linker is guessing (and guessing wrong) about where to emit the destructor code. Don't forget that even a default (empty) destructor may have automatically-generated code.

Stephen M. Webb
Professional Free Software Developer

Well, this time i've got some inner problems with this code.

At fisrt it seems to run fine.
But after i tried to use the implementation trhough the interface i get Segment violation.I used GDB to see what's going on.

What i've got is that in dlopen(where i open the shared library) this messages appear:

Quote:
[Thread debugging using libthread_db enabled]
[New Thread 0x7f44966db700 (LWP 5936)]
Error while reading shared library symbols:
Cannot find new threads: generic error
.
.
Program received signal SIGSEGV, Segmentation fault.



I still don't get why it can't find the correct symbols. I mean, the .so is not in a system dir, its inside the project folder hirarchy, and i load it explicitly.
Any suggestions?
I don't really have any idea regarding that seg fault, but something occurred to me: that pure virtual class that you implement in your shared object, it's located in your executable, right?

the problem is that by default, symbols from the executable itself aren't exported and thus shared object can't import them. You have to pass the -E option to the linker when linking the executable (using -Wl,-E when invoking gcc if you link by calling the gcc front-end) for that.

There are some infos about how those things can cause issues in the gcc faq: http://gcc.gnu.org/faq.html#dso
Quote:Original post by Dospro
Do i really have to create a cpp just to solve this problem? i mean, neither the constructor nor the destructor actually have any code, so, what other way, can this be solved?


The problem. The ctor/dtor have not been compiled into the *.so by the compiler. Whether they do or do not have any code is entirely moot.

The solution. Somewhere in your code - at least once - you must have included that header file into a cpp file. The simple act of doing that, will allow the compiler to compile those empty ctor/dtors into empty code blocks in your so. C++ compilers do not compile header files.

As for your segfault, my guess from looking at your func prototypes is that you're doing something very dumb indeed... I'm guessing that you're are doing this in your dll:

bool createRenderDevice(cDPI_Render *dpird_ptr){  // the address will be lost when you exit the scope.   dpird_ptr = new dllRenderer();  return true;}


which is pretty darned wrong if you ask me. surely it should be this... ?

bool createRenderDevice(cDPI_Render** dpird_ptr){  *dpird_ptr = new dllRenderer();  return true;}


Which would explain why you seg fault since the pointer passed to createRenderDevice will still be un-initialised after the function is called.
Quote:
The solution. Somewhere in your code - at least once - you must have included that header file into a cpp file. The simple act of doing that, will allow the compiler to compile those empty ctor/dtors into empty code blocks in your so. C++ compilers do not compile header files.


Well, actually, i include the header file inside the .so cpp.

About the wrong code, i don't understand why is the address lost?
I pass a pointer and when i use the operator new the pointer points to this noew address? Am i wrong? Why should i need double pointers?
Quote:Original post by Dospro
About the wrong code, i don't understand why is the address lost?
I pass a pointer and when i use the operator new the pointer points to this noew address? Am i wrong? Why should i need double pointers?


Google 'passing argument by value vs by reference in C++' and you shall have your answer. I'm guessing you skipped all of the chapters on functions and pointers.....

void func(int a){  a = 22;}int b=1;func(b);cout << b << endl; // 1


And that is what your code does:

void func(int* a){  a = new int;}int* b = 0;func(b);cout << b << endl; // prints 0


once you've understood the difference about passing args by value vs reference, you'd realise pretty quickly that you are trying to modify the value of b within func, which means you have to pass it by reference - not value as you are doing. i.e.

void func(int** a){  *a = new int;}int* b = 0;func(&b);cout << b << endl; // prints some random address


or even


void func(int*& a){  a = new int;}int* b = 0;func(b);cout << b << endl; // prints some random address
Well. i already understand quite well, both type of argument passing.
What i didn't actually know was that pointers can actually be passed as reference.
Still, kind of confusing, but works.
Thanks, now everything works fine.
Thanks everybody.

This topic is closed to new replies.

Advertisement