teplov-it Posted August 14, 2017 Posted August 14, 2017 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.
silent Posted August 14, 2017 Posted August 14, 2017 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: ftp://files.unigine.com user: upload password: 6xYkd6vLYWjpW6SN
teplov-it Posted August 14, 2017 Author Posted August 14, 2017 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.
alexander Posted August 14, 2017 Posted August 14, 2017 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
teplov-it Posted August 14, 2017 Author Posted August 14, 2017 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).
alexander Posted August 15, 2017 Posted August 15, 2017 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
teplov-it Posted August 17, 2017 Author Posted August 17, 2017 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?
alexander Posted August 17, 2017 Posted August 17, 2017 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"); 1
teplov-it Posted August 18, 2017 Author Posted August 18, 2017 Thank you, alexander! It works. I think I have understand how all work. Maybe can you improve plugin creation documentation to avoid future questions?
teplov-it Posted August 22, 2017 Author Posted August 22, 2017 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.
alexander Posted August 24, 2017 Posted August 24, 2017 Sorry, my bad. We try to use cast between two parallel classes: Don't do multi-inheritance in this case - is the correct way. // 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 1
teplov-it Posted August 24, 2017 Author Posted August 24, 2017 alexander, thank you! I'm so sorry to bother you. The problem is completely solved. Everything is working fine now.
Recommended Posts