Amazing Idea went wrong: Creating polymorphic Methods among DLL and Client!

Started by
3 comments, last by wintertime 4 years, 11 months ago

There are two projects. One is called VampEngine (a Shared Library) and the other Application (A console application).

The VampEngine contains a VampEngine::Core class. This function contains a void VampEngine::Core::MainLoop() method.

There are also two extern C functions void *Vamp_Core_Constructor() and Vamp_Core_MainLoop(void *obj)

Now, this is what happens:

The client is calling void *Vamp_Core_Constructor() in order to create a VampEngine::Core which lives inside the Shared Library. And also casts this void object into a Core object.

The void VampEngine::Core::MainLoop() is being implemented both in the Shared Library and in the client. The client's MainLoop() is just a wrapper for calling the extern c function Vamp_Core_MainLoop(void *obj)

The code compiles and links in both Windows and Linux. When you run it on Windows, MainLoop() (shared library's implementation) is getting called, but on Linux, there is a recursive call in the client's MainLoop() implementation.

The behavior in each Operating System can be shown below:

Untitled-Diagram.jpg

asd.jpg

You can check the project (It's small, I just started it ) On my GitHub Repo . Also, I have a Premake script if you want to check the code for yourself. You are most interested in the files:

API.h  EntryPoint.h  Application.hpp  Core.cpp  Core.h  Core.hpp  Main.cpp

 

 

This might explain the problem in a better way:

The Core class has two implementations of the Core::MainLoop(). One is implemented inside the DLL and the other in the client. The client's implementation is just a wrapper which calls a c extern function (which lives in the dll) which actually calls the MainLoop() implemented inside the DLL. 

Now in visual c++, because I'm only exporting that c extern function, Core::MainLoop() acts in a polymorphic way. The client calls his implementation of Core::MainLoop() which calls the extern function and eventually the Core::MainLoop() inside the dll runs.

On linux, I believe by default all the symbols are getting exported. And this is the behavior I saw using a debugger:

The client calls Core::MainLoop() , this calls the extern function, the extern function calls again Core::MainLoop() but instead of running the dll implementation it actually runs the the clients implementation. It's like the clients Core::MainLoop() is calling itself over and over again!

 

Conclusion:

I located the above behavior using gdb debugger on Linux. I just noticed the client's implementation was getting called in a recursive way instead of acting polymorphically ending up calling the implementation of the dll's MainLoop()


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

Advertisement

What's the point of returning void* here if your client application has to cast to Core* anyways?


void * Vamp_Core_Constructor(const char* title, unsigned int width, unsigned int height)

As for your problem, your client contains


namespace VampEngine
{
	void Core::MainLoop()
	{
		Vamp_Core_MainLoop((void *)this);
	}
}

but your .dll also has this method defined


namespace VampEngine
{
	void Core::MainLoop()
	{
		/* omitted */
	}
}

For reasons unknown to me linked does not see this as a problem when 2nd method is inside .dll and just chooses any of them, which happens to be a different method on Windows and Linux. If you try to build your .dll as static library the problem becomes apparent:


2>VampEngine.lib(Core.obj) : error LNK2005: "public: void __cdecl VampEngine::Core::MainLoop(void)" (?MainLoop@Core@VampEngine@@QEAAXXZ) already defined in Main.obj

 

Yes that's the problem, but I wasn't getting any "Already Defined errors". The thing is that on Windows it was actually working, probably because visual c++ was assigning a different name for the implementation into the dll and a different in the clients right?

Now on Linux, stackoverflow told me that gcc had to choose between those two implementations and it was choosing the clients implementation.

I found the solution to my problem though. I will create a CoreImpl class into the dll and a Core class in the client. The client will then create a Core object which it's constructor will actually call a c function (factory function or how it is called) to construct a CoreImpl object inside the dll. Then the Core class will have the appropriate wrapping methods which with the use of c functions and a CoreImpl object reference, will call any CoreImpl method.


void life()
{
  while (!succeed())
    try_again();

  die_happily();
}

 

You are violating the one definition rule; with the resulting Undefined Behaviour anything can happen. You shouldn't rely on that always being the same.

This topic is closed to new replies.

Advertisement