Jump to content

[SOLVED] Using custom plugin in c++ app


photo

Recommended Posts

Hello.

I would like to use custom plugins (namely external classes) in c++ project, but I get linker error (LNK2019) when using plugin from your sample (as described here, i.e with creating separate header file) from c++ side. Also, with third-party plugin I have the same issue. From Script Side everything is working fine. What am I doing wrong?

IDE: VS2017 Community.

Link to comment

If you can't even build and link included sample of C++ plugin, I'm afraid it's related to the Visual Studio version. Officially we do support MSVS 2013 (all samples can be build and run without any issues).

We do have only a single plugin (Interface) that exposes it's API functions to both UnigineScript and C++ (via separate header) - have you tried to test it as well? Have you also tried to use Visual Studio 2013 to verify if it's compiler issue or not?

Thanks! 

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to comment

I have just tried MSVS 2013, nothing change.

What I have done:

1) Created new c++ project.

2) Created header file for sample plugin and include it into project (AppWorldLogic.h):

#include <UnigineInterface.h>
#include <UnigineLogic.h>
#include <UniginePlugin.h>

using namespace Unigine;

//////////////////////////////////////////////////////////////////////////
// MyFunction
//////////////////////////////////////////////////////////////////////////

void my_print(const char *str);

//////////////////////////////////////////////////////////////////////////
// MyExternClass
//////////////////////////////////////////////////////////////////////////

class MyExternClass
{
public:
	MyExternClass();
	~MyExternClass();

	// print
	void print(const char *str);
};

//////////////////////////////////////////////////////////////////////////
// MySystemLogic
//////////////////////////////////////////////////////////////////////////

class MySystemLogic : public SystemLogic
{
public:
	// initialize world
	virtual int init();

	// shutdown world
	virtual int shutdown();
};

//////////////////////////////////////////////////////////////////////////
// MyWorldLogic
//////////////////////////////////////////////////////////////////////////

class MyWorldLogic : public WorldLogic
{
public:
	// initialize world
	virtual int init();

	// shutdown world
	virtual int shutdown();
};

//////////////////////////////////////////////////////////////////////////
// MyEditorLogic
//////////////////////////////////////////////////////////////////////////

class MyEditorLogic : public EditorLogic
{
public:
	// initialize world
	virtual int init();

	// shutdown world
	virtual int shutdown();
};

//////////////////////////////////////////////////////////////////////////
// MyPlugin
//////////////////////////////////////////////////////////////////////////

class MyPlugin : public Plugin
{
public:
	MyPlugin();
	virtual ~MyPlugin();

	// plugin data
	virtual void *get_data()
	{
		return this;
	}

	// initialize plugin
	virtual int init();

	// shutdown plugin
	virtual int shutdown();

	// destroy plugin
	virtual void destroy();

private:
	MySystemLogic system_logic;
	MyWorldLogic world_logic;
	MyEditorLogic editor_logic;
};

3) Copied sample plugin dll into bin folder of new project.

4) In AppWorldLogic.cpp wrote next:

	EnginePtr engine;
	// specify a path to the plugin relative to the binary executable
	int id = engine->findPlugin("plugin");
	if (id == -1) {
		Log::message("Cannot find the plugin\n");
		return 0;
	}
	// 2. Get the plugin interface
	Plugin *plugin = Engine::get()->getPluginInterface(id);
	if (!plugin) {
		Log::message("The plugin is null\n");
		return 0;
	}

	// 3. Cast the plugin interface to your plugin class
	MyPlugin *pluginm = static_cast<MyPlugin*>(plugin);
	if (!pluginm) {
		Log::message("Cannot cast the plugin\n");
		return 0;
	}

	MyExternClass *t = new MyExternClass(); //LNK2019
	t->print("hello"); //LNK2019

5) Compiled project, but recieved LNK2019.

Link to comment

Hi!

Did you forget to insert this code into your header file?

// 1. Define the function required for adding the plugin library.
// It returns a new instance of the custom plugin
extern "C" UNIGINE_EXPORT void *CreatePlugin() {
	return new MyPlugin();
}

// 2. Define the function required for releasing the plugin library.
// For that, a void pointer should be casted to the custom plugin type and then deleted
extern "C" UNIGINE_EXPORT void ReleasePlugin(void *plugin) {
	delete static_cast<MyPlugin*>(plugin);
}

https://developer.unigine.com/en/docs/2.5/code/cpp/plugin

Best regards,
Alexander

Link to comment

alexander, hi.

I use plugin from your sample (samples\Api\Systems\Plugins) for testing. Only two thing that I have done: I have written header file for your's cpp file, because it's nesseccary according to manual, and have copied dll into bin folder. I have also edited unigine.cfg (extern_plugin field). If I insert this code in header now I recieve that these function already defined, if I add only functions' headers - nothing change from my previous situation (working fine from script side and problems from c++ side).

Link to comment

Aah ... I undestand you.


Did you include "plugin.h", but didn't add "plugin.cpp" (your plugin source) to your solution project? Of course, you get an "unresolved external symbol" in this case.
C++ doesn't work that way. The linker needs to know the address of the object constructor (in your example, it's MyExternClass) in order to be able to create them. 
Since dll is loaded at run-time, the linker can't resolve this situation. So, simply creating header files for plugins will not work.

You need to change the plugin source that way:
1) Write a new header file with fully abstract classes (with pure virtual methods) that are used in your plugin.
2) Remove all constructors (from abstract class) and create methods that call constructor inside (for example, add virtual MyExternClass* createMyExternClass() const = 0).
3) Inherit plugin classes from the created abstract class.

An example of such a realization is in the "Interface" plugin in your SDK folder: Unigine SDK Browser\sdks\sim_windows_[VERISON]\source\plugins\Interface\Interface\InterfaceBase.h

Best regards,
Alexander

Link to comment

Hmm, I'm afraid that I didn't totally understand.

I have done this:

1) I have written MyExternClassBase in header file:

class MyExternClass
{
public:
	virtual ~MyExternClass() {};

	// print
	virtual void print(const char *str) const = 0;
	virtual MyExternClassBase* createMyExternClass() const = 0;
};

2) I've inherited MyExternClass:

class MyExternClass : public MyExternClassBase
{
public:
	MyExternClass();
	virtual ~MyExternClass();

	// print
	virtual void print(const char *str) const;
 	virtual MyExternClassBase* createMyExternClass() const;
};

and have added in cpp file next:

MyExternClass::MyExternClassBase* createMyExternClass() const
{
	return new MyExternClass();
};

3) Then I rebuild library, copy header file to source folder and dll to bin.

Script side still work but I don't understand, how can I create class' instance from c++ side? Maybe must I create static MyExternClassBase* createMyExternClass() or use singleton as in interface plugin? And if I must use singleton how should it be done in plugin?

Link to comment

Hi!

Look at the InterfacePlugin.cpp file. It is the main file of this plugin. In private block you can see: Interface *interface; - it's a pointer to the Interface class inherited from InterfaceBase class (which is open to C++ API users). When you load Interface plugin, it create Interface object (inside init()) and can return it's pointer via get_data() method.

What C++ users should do to access an Interface?

int index = Engine::get()->findPlugin("Interface");
InterfaceBase* interface_plugin = (InterfaceBase*)Engine::get()->getPluginData(index); // this function call get_data() method

 

What can you do in your situation?
1) Inherit MyPlugin from some interface. For example:

class MyPlugin : public Plugin, public virtual MyPluginBase

2) In your MyPluginBase class put virtual method to creation MyExternClass object:

class MyPluginBase
{
public:
  virtual MyExternClassBase* createMyExternClass() = 0;
}

...and realize it in MyPlugin.

So... Finally, user should have header with MyPluginBase and MyExternClassBase descriptions only.
In his code, he should only write this:

	// 2. Get the plugin interface
	Plugin *plugin = Engine::get()->getPluginInterface(id);
	if (!plugin) {
		Log::message("The plugin is null\n");
		return 0;
	}

	// 3. Cast the plugin interface to your plugin class
	MyPluginBase *plugin_base = static_cast<MyPluginBase*>(plugin);
	if (!plugin_base) {
		Log::message("Cannot cast the plugin\n");
		return 0;
	}

	MyExternClassBase *t = plugin_base->createMyExternClass();
	t->print("hello");
  • Like 1
Link to comment

Thank you, alexander!

It works. I think I have understand how all work. Maybe can you improve plugin creation documentation to avoid future questions?

Link to comment

Hi again.

Maybe I spoke too soon. The project is compiled but it doesn't work properly: createMyExternClass method doesn't call for some reason (or doesn't call correct implementation).

Can you help me? Where am I wrong?

// bases.h, this file include in main project
class MyExternClassBase
{
public:
	virtual ~MyExternClassBase() {}

	// print
	virtual void print(const char *str) = 0;
};

class MyPluginBase
{
public:
	virtual MyExternClassBase* createMyExternClass() = 0;
};
// plugin.h
#include <UnigineInterface.h>
#include <UnigineLogic.h>
#include <UniginePlugin.h>

#include "bases.h"

class MyExternClass : public MyExternClassBase
{
public:
	MyExternClass();
	virtual ~MyExternClass() {}

	// print
	virtual void print(const char *str);
};

class MyPlugin : public Unigine::Plugin, public virtual MyPluginBase
{
public:
	MyPlugin();
	virtual ~MyPlugin();

	// plugin data
	virtual void *get_data()
	{
		return this;
	}

	// initialize plugin
	virtual int init();

	// shutdown plugin
	virtual int shutdown();

	// destroy plugin
	virtual void destroy();
	virtual MyExternClassBase* createMyExternClass();

private:
};

extern "C" UNIGINE_EXPORT void *CreatePlugin()
{
	return new MyPlugin();
}

extern "C" UNIGINE_EXPORT void ReleasePlugin(void *plugin)
{
	delete static_cast<MyPlugin *>(plugin);
}
//plugin.cpp
#include "plugin.h"

using namespace Unigine;

MyExternClass::MyExternClass()
{
	Log::message("MyExternClass::MyExternClass(): called\n");
}

void MyExternClass::print(const char *str)
{
	Log::message("MyExternClass::print(\"%s\"): called\n", str);
}

MyPlugin::MyPlugin()
{
}

MyPlugin::~MyPlugin()
{
}

int MyPlugin::init()
{
	Log::message("MyPlugin::init(): called\n");

	// create interpreter group
	int id = Interpreter::addGroup("MyPlugin");

	// export extern class
	ExternClass<MyExternClass> *my_class = MakeExternClass<MyExternClass>();
	my_class->addConstructor();
	my_class->addFunction("print", &MyExternClass::print);
	Interpreter::addExternClass("MyExternClass", my_class, id);


	return 1;
}

int MyPlugin::shutdown()
{
	Log::message("MyPlugin::shutdown(): called\n");

	// remove interpreter resources
	Interpreter::removeGroup("MyPlugin");

	return 1;
}

void MyPlugin::destroy()
{
	Log::message("MyPlugin::destroy(): called\n");
}

MyExternClassBase * MyPlugin::createMyExternClass()
{
	Log::message("MyPlugin::createMyExternClass(): called\n");
	return new MyExternClass();
}

And main project:

#include "bases.h"

  ...
  
int AppWorldLogic::init() {
	// Write here code to be called on world initialization: initialize resources for your world scene during the world start.
	EnginePtr engine;
	// specify a path to the plugin relative to the binary executable
	int id = engine->findPlugin("plugin");
	if (id == -1) {
		Log::message("Cannot find the plugin\n");
		return 0;
	}
	// 2. Get the plugin interface
	Plugin *plugin = Engine::get()->getPluginInterface(id);
	if (!plugin) {
		Log::message("The plugin is null\n");
		return 0;
	}

	// 3. Cast the plugin interface to your plugin class
	MyPluginBase *pluginm = (MyPluginBase*)(plugin);
	if (!pluginm) {
		Log::message("Cannot cast the plugin\n");
		return 0;
	}

	MyExternClassBase *t = pluginm->createMyExternClass();

I also tried get with getPluginData method but change nothing.

And finally I tried realize MyPlugin like InterfacePlugin (MyExternClass is created in plugin). But if I use this variant I have noticed that perfomance falls slightly.

Link to comment

Sorry, my bad.

We try to use cast between two parallel classes: f1ed14a3b489ae12d07afda714f0fdea.png

Don't do multi-inheritance in this case - is the correct way.
121f2434870a0c9065ea7e5f92fd0329.png

// bases.h, this file include in main project
#include <UniginePlugin.h>

class MyExternClassBase
{
public:
	virtual ~MyExternClassBase() {}

	// print
	virtual void print(const char *str) = 0;
};

class MyPluginBase : public Unigine::Plugin
{
public:
	virtual MyExternClassBase* createMyExternClass() = 0;
};
// plugin.h
#include <UnigineInterface.h>
#include <UnigineLogic.h>
#include <UniginePlugin.h>

#include "bases.h"

class MyExternClass : public MyExternClassBase
{
public:
	MyExternClass();
	virtual ~MyExternClass() {}

	// print
	virtual void print(const char *str);
};

class MyPlugin : public MyPluginBase
{
public:
	MyPlugin();
	virtual ~MyPlugin();

	// plugin data
	virtual void *get_data()
	{
		return this;
	}

	// initialize plugin
	virtual int init();

	// shutdown plugin
	virtual int shutdown();

	// destroy plugin
	virtual void destroy();
	virtual MyExternClassBase* createMyExternClass();

private:
};

extern "C" UNIGINE_EXPORT void *CreatePlugin()
{
	return new MyPlugin();
}

extern "C" UNIGINE_EXPORT void ReleasePlugin(void *plugin)
{
	delete static_cast<MyPlugin *>(plugin);
}

 

Best regards,
Alexander

  • Like 1
Link to comment
  • silent changed the title to [SOLVED] Using custom plugin in c++ app
×
×
  • Create New...