connect: External "C" function
-
I developed a Qt GUI C++
shared library
(plugin) that contains a Fortran 90 static library inside of it.To enable to receive the Fortran data back to C++ side, I created a Fortran/C++ interface and it works great.
In the C++ side I have:
external "C" { void callFortran(...input arguments ....); void fortranMsg(int msgType, char* msg); }
So, when Fortran send a message to C++, I already receive that inside of this
external C
function below:void fortranMsg(int msgType, char* msg) { // Messages from Fortran subroutine to C++ side // It is already working! }
I would like to
connect
thisfortranMsg()
function to a plugin method, like:Plugin::Plugin(QObject* parent) { // WRONG SINTAX: JUST A EXAMPLE bool conn = connect(fortranMsg, this, &Plugin::receivedMsg); } Plugin::receivedMsg(int msgType, char* msg) { // Get that Fortran message inside a plugin method }
I don't know if
connect
is a good way to do that...it is just an example of what I need to do...How can I send the data (
msgType
andmsg
) fromfortranMsg()
to aPlugin::receivedMsg()
?Thank you,
-
@fem_dev Thank you @JKSH @jsulm and @JonB for all help in this topic! I really need to say "thank you"! You all are great!
With your help I finally solve this problem.
Here is my
plugin.h
header file:extern Plugin_API *pluginInstance; // <-- 1. CREATE A GLOBAL INTERFACE CLASS POINTER extern "C" { void fortranMsg(int msgType, char* msg); } class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) public: ... void sendStatusMsg(const QString &msg) override; }
Here is my
plugin.cpp
implementation file:Plugin_API* pluginInstance = nullptr; // <-- 2. Init the global pointer to nullptr Plugin::Plugin(QObject* parent) { pluginInstance = this; // <-- 3. Point to the current object }
Now, I can use the global variable
pluginInstance
inside a pure function too.
With this pointer, I can call anyPlugin
method from any pure function. Like:void fortranMsg(int msgType, char* msg) { pluginInstance->sendStatusMsg(QString(msg)); }
That's it!
Thank you all again!
-
@fem_dev said in connect: External "C" function:
How can I send the data (
msgType
andmsg
) fromfortranMsg()
to aPlugin::receivedMsg()
?Since
fortranMsg()
is a function that is called by Fortran, you cannot connect it like a signal. Instead, you must add extra code insidefortranMsg()
:void fortranMsg(int msgType, char* msg) { // Messages from Fortran subroutine to C++ side // It is already working! exportData(msgType, msg); // <-- Add a function call here to pass the data out }
There are many ways you could implement the exporting function. For example, you could store a pointer to the plugin object as a global variable and call
receivedMsg()
directly:Plugin *pluginInstance = nullptr; // This pointer can be updated when you construct your plugin object void fortranMsg(int msgType, char* msg) { // Messages from Fortran subroutine to C++ side // It is already working! if (pluginInstance) pluginInstsance->receivedMsg(msgType, msg); }
-
Thank you @JKSH for your quick response...
@JKSH said in connect: External "C" function:
There are many ways you could implement the exporting function.
I think that I need some help to understand in which scope should I write this lines...
The problem is: Only inside the main application I can write something like this:
Plugin *pluginInstance = nullptr; // This pointer can be updated when you construct your plugin object
Because each plugin is loaded by the main application using
QPluginLoader
.
So, I think that I can't use this line above inside the Plugin class. Am I right?Here is my
plugin.h
header file:#include "dialog.h" #include "plugin_global.h" #include "plugin_qt_api.h" // Fortran subroutines extern "C" { void callFortran(...input arguments ....); void fortranMsg(int msgType, char* msg); } class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) private: Dialog _ui; public: explicit Plugin(QObject* parent = nullptr); ~Plugin(); //... All Methods override void receiveMsg(int msgType, char* msg); };
Here is my
plugin.cpp
implementation:#include <iostream> #include "plugin.h" #include "dialog.h" Plugin::Plugin(QObject* parent) { } Plugin::~Plugin() { } void Plugin::receivedMsg(int msgType, char* msg) { } void fortranMsg(int msgType, char* msg) { std::cout << msgType << " | " << msg << std::endl; // SEND DATA TO A PLUGIN METHOD // HOW? }
Could you help me pass this data from the
fortranMsg ()
function to thePlugin :: receivedMsg ()
method?A first thought that comes to mind is that I should try to make this connection entirely within the
shared library
(plugin) project, without resorting to the main application project.
Or is there a better way to "connect" via themain application
? -
@fem_dev said in connect: External "C" function:
// SEND DATA TO A PLUGIN METHOD
// HOW?Like @JKSH suggested.
Add a header file and putextern Plugin *pluginInstance;
there. Then define pluginInstance in one of your cpp files (main.cpp for example). Include the header file in the cpp file where you define fortranMsg() and you can call pluginInstance as suggested.
-
@jsulm Thank you...I'm almost there...
Here is what I did:
#include <iostream> #include "plugin.h" #include "dialog.h" extern Plugin *pluginInstance; Plugin::Plugin(QObject* parent) { } void Plugin::receivedMsg(int msgType, char* msg) { } void fortranMsg(int msgType, char* msg) { std::cout << msgType << " | " << msg << std::endl; if (pluginInstance) pluginInstance->receivedMsg(msgType, msg); }
The
shared library
(plugin) compiles without any error or warning. All ok!But when my
main application
tries to load thisshared library
(usingQPluginLoader
) I got this run-time error:Cannot load library libLobular-Bearing.so: undefined symbol: pluginInstance
What is missing? How can I fix it?
Thank you,
-
@fem_dev
You are missing where @jsulm / @JKSH said:Then define pluginInstance in one of your cpp files (main.cpp for example)
You need the line reading
Plugin *pluginInstance = nullptr; // This pointer can be updated when you construct your plugin object
in your main application. Once, in one of its
.cpp
files.. Note that is without theextern
. Where do you have this?Explanation:
Your shared library has
extern Plugin *pluginInstance;
in it. That means it says "there is/must be apluginInstance
out there, which I can use". That is whatextern
means. So it compiles.When you come to load at run-time, your program needs to have defined this
pluginInstance
, to provide this variable. Without it you get undefined symbol. The statementPlugin *pluginInstance
, without theextern
, defines the symbol. That is needed in some one (and only one).cpp
file of the main application loading your shared library. -
@JonB Almost there...
My
shared library
(plugin) have a Interface Class calledPlugin_API
as you can see below:class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) ... }
So, inside my
main application
I have the samePlugin_API
Interface...not thePlugin
class.
I think that theextern
variable declaration must bePluginAPI
type in both Qt projects (plugin
andmain application
), right?#include <iostream> #include "plugin.h" #include "dialog.h" extern Plugin_API *pluginInstance; // Changed from 'Plugin' to 'Plugin_API' Plugin::Plugin(QObject* parent) { } void Plugin::receivedMsg(int msgType, char* msg) { } void fortranMsg(int msgType, char* msg) { std::cout << msgType << " | " << msg << std::endl; if (pluginInstance) pluginInstance->receivedMsg(msgType, msg); }
In my
main application
project, I opened the main GUI window Widget (called:App.cpp
) and tried to add this line below:#include "app.h" #include "ui_app.h" Plugin_API *pluginInstance = nullptr; App::App(QWidget *parent) : QMainWindow(parent) , ui(new Ui::App) { ui->setupUi(this); ... }
But I got the same run time error:
libLobular-Bearing.so: undefined symbol: pluginInstance
I tried to change the pointer value inside the
App:loadPlugins()
method (as you can see below) but without success too.void App::loadPlugins(const QStringList pluginsList) { foreach(QString file, pluginsList) { QPluginLoader loader(file); // Error checking: if (!loader.load()) { sendErrorMsg(tr("%1: ").arg(loader.errorString())); continue; } // Get and display the plugin file name: const QStringList pathSplited = loader.fileName().split("/"); const QString fileName = pathSplited.last(); sendStatusMsg(tr("Loading plugin: %1").arg(fileName)); // Cast plugin interface: Plugin_API* plugin = qobject_cast<Plugin_API*>(loader.instance()); // Error chekcing: nullptr if (!plugin) { sendErrorMsg(tr("Wrong Plugin API! Could not cast: %1").arg(loader.fileName())); continue; } // ===== CONNECT ALL SIGNALS / SLOTS ===== // bool conn1 = connect(plugin, &Plugin_API::sendMsg, this, &App::receivedMsg); bool conn2 = connect(plugin, &Plugin_API::savePluginSettings, this, &App::receivedPluginSettings); bool allConnections = conn1 & conn2; if (!allConnections) { sendErrorMsg(tr("Unable to connect: %1").arg(plugin->getName())); continue; } pluginInstance = plugin; // TRY TO USE THE POINTER // Store the plugin in a map: _pluginsMapMap[plugin->getType()][plugin->getName()] = plugin; } }
Could you please help me a little bit more?
How can I fix it? -
@fem_dev said in connect: External "C" function:
In my main application project, I opened the main GUI window Widget (called: App.cpp) and tried to add this line below:
This does not make sense as pluginInstance is declared in the plug-in NOT in the application! You do not need that in main application at all - simply include the header file which contains extern Plugin_API *pluginInstance; - this is what I actually suggested before.
To make it short:- Add extern Plugin_API *pluginInstance; to the header file inside plug-in NOT cpp file
- Add Plugin_API *pluginInstance = nullptr; to the corresponding cpp file inside plug-in
- Do NOT add Plugin_API *pluginInstance = nullptr; in main application
- Read about "extern", for example https://riptutorial.com/cplusplus/example/28730/extern
But, I'm wondering why you don't simply add pluginInstance as static member to Plugin_API...
-
@fem_dev
Briefly, I had slightly misunderstood, and taken your word thatpluginInstance
was to be defined in your main program. From @jsulm I now understand it is to work the other way round: you will define it in your shared library, and export it (extern
) to be accessible from your main. The explanation aboutextern Plugin_API *pluginInstance
declaration can appear many times (e.g. in a.h
file) while thePlugin_API *pluginInstance
definition must appear just once in a.cpp
file remains the same.You should follow exactly what @jsulm has instructed. If as he says you do not need to access
pluginInstance
outside of the shared library at all, you may be able to just define it asstatic
there. -
@fem_dev said in connect: External "C" function:
Because each plugin is loaded by the main application using
QPluginLoader
.
So, I think that I can't use this line above inside the Plugin class. Am I right?You are right, the example code that I wrote must be placed in your main application, not your plugin.
Note: That example expects a single plugin instance. If you want to load multiple plugins at the same time, then you'll need a vector of pointers, not just a single pointer.
A first thought that comes to mind is that I should try to make this connection entirely within the
shared library
(plugin) project, without resorting to the main application project.I don't think that's a good design. A plugin should not be aware of the main application, so a plugin shouldn't be expected to know that
fortranMsg()
exists.The main application should load the plugin and make all necessary "connections" to the plugin.
Or is there a better way to "connect" via the
main application
?Let's step back a bit. Ignore the Fortran code for now.
How do you currently pass any data from your main application into your plugin objects?
-
@jsulm thank you...I'm almost there...
Here are my modifications:Here is my
plugin.h
header file:#include "dialog.h" #include "plugin_global.h" #include "plugin_qt_api.h" extern Plugin_API *pluginInstance; // FIRST MODIFICATION // Fortran interface extern "C" { void fortranMsg(int msgType, char* msg); } class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) private: Dialog _ui; public: explicit Plugin(QObject* parent = nullptr); ~Plugin(); ... }
Here is my
plugin.cpp
implementation file:#include "plugin.h" #include <iostream> #include "dialog.h" Plugin_API* pluginInstance = nullptr; // SECOND MODIFICATION Plugin::Plugin(QObject* parent) { Q_UNUSED(parent) } // Other Plugin method implementations...
And inside the
plugin.cpp
I have:void fortranMsg(int msgType, char* msg) { std::cout << msgType << " | " << msg << std::endl; // PRINT THE MSG IN THE Qt CONSOLE OK if (pluginInstance) { pluginInstance->sendStatusMsg(QString(msg)); } else { std::cout << "pluginInstance is NULL" << std::endl; // ALWAYS NULLPTR } }
I do NOT declared `pluginInstance` in my `main application` // LAST MODIFICATION
I got a clean build. No errors, no warnings! All good!
And I don't got any run-time erros, BUT I got always the same console message:pluginInstance is NULL
After that, I tried too:
Plugin_API* pluginInstance = nullptr; // SECOND MODIFICATION Plugin::Plugin(QObject* parent) { Q_UNUSED(parent) pluginInstance = new Plugin; // CREATE A VALID INSTANCE }
In this case, the
pluginInstance is NULL
console message disapears (the pointer is valid), BUT it is not calling correctly thePlugin::sendStatusMsg()
method as I want.
So, I don't know why, I don't get the sent plugin message in mymain application
.@JKSH here is how I communicate my own
Plugin
with mymain application
:
First, theplugin_qt_api.h
:#ifndef PLUGIN_QT_API_H #define PLUGIN_QT_API_H #include <QtPlugin> #include <QString> #include "message_types.h" class Plugin_API : public QObject { Q_OBJECT public: Plugin_API() {} virtual ~Plugin_API() = default; ... virtual void sendStatusMsg(const QString& msg) = 0; signals: void sendMsg(QString source, MSG_TYPE msgType, QString msg); }; Q_DECLARE_INTERFACE(Plugin_API, "com.rotortest.plugin") #endif // PLUGIN_QT_API_H
Second, the
plugin.h
:#include "plugin_global.h" #include "plugin_qt_api.h" extern Plugin_API *pluginInstance; // Fortran subroutines extern "C" { void fortranMsg(int msgType, char* msg); } class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) public: explicit Plugin(QObject* parent = nullptr); ~Plugin(); ... void sendStatusMsg(const QString &msg) override; };
Finally, the
Plugin::sendStatusMsg()
method implementation inside theplugin.cpp
file:void Plugin::sendStatusMsg(const QString& msg) { emit sendMsg("My Plugin", MSG_TYPE::STATUS_MSG, msg); }
This plugin method works good if I call it from a method inside the
Plugin
class. No problems here!
My problem is ONLY when I have to call this method from thefortranMsg()
function. Because it is a function and NOT aPlugin
method.So, when my Fortran send a message to the C++ side, I got the message correctly in the
fortranMsg()
function (I can see it on Qt console), but I don't know how to pass this received message to thePlugin::sendStatusMsg()
method.I want to do that to enable my plugins always use the same plugin method to send messages from the plugin side to my main application.
As you can see above, inside my
main application
, I have a method calledApp::loadPlugin()
.
Inside of it, I have:@fem_dev said in connect: External "C" function:
bool conn1 = connect(plugin, &Plugin_API::sendMsg, this, &App::receivedMsg);
Could you help me?
-
@fem_dev Thank you @JKSH @jsulm and @JonB for all help in this topic! I really need to say "thank you"! You all are great!
With your help I finally solve this problem.
Here is my
plugin.h
header file:extern Plugin_API *pluginInstance; // <-- 1. CREATE A GLOBAL INTERFACE CLASS POINTER extern "C" { void fortranMsg(int msgType, char* msg); } class PLUGIN_EXPORT Plugin : public Plugin_API { Q_OBJECT Q_PLUGIN_METADATA(IID "com.rotortest.plugin") Q_INTERFACES(Plugin_API) public: ... void sendStatusMsg(const QString &msg) override; }
Here is my
plugin.cpp
implementation file:Plugin_API* pluginInstance = nullptr; // <-- 2. Init the global pointer to nullptr Plugin::Plugin(QObject* parent) { pluginInstance = this; // <-- 3. Point to the current object }
Now, I can use the global variable
pluginInstance
inside a pure function too.
With this pointer, I can call anyPlugin
method from any pure function. Like:void fortranMsg(int msgType, char* msg) { pluginInstance->sendStatusMsg(QString(msg)); }
That's it!
Thank you all again!
-
@JKSH said in connect: External "C" function:
only loads 1 instance of the plugin
No @JKSH ...my
main application
should load many plugins...What part of my code above made you think that my main application only loads a single plugin?
Did I write something wrong?
-
@fem_dev said in connect: External "C" function:
No @JKSH ...my
main application
should load many plugins...What part of my code above made you think that my main application only loads a single plugin?
You only have 1
pluginInstance
pointer. That meansfortranMsg()
can only send the message to 1 plugin. -
@JKSH said in connect: External "C" function:
You only have 1 pluginInstance pointer.
Yes! That's right!
Inside my plugin, I have only one
pluginInstance
pointer.Each plugin is a isolated Qt
shared library
project. So inside of each plugin I must have only your ownpluginInstance
pointer.My
main application
will load all compiled plugins after that.