Problem indirectly linking a wrapper to QtCore in non-qt dll
-
Because there doesn't seem to be a way around the issue I posted in this article, I am rewriting my application as a mixed mode .NET application. This includes my original Qt based "core", a C++ CLI (.NET) wrapper for that core; and a C# program that uses the wrapper which handles the user interface and drawing a control quickly enough to use (as it worked just fine before).
Right now I know that my implementation is incomplete. I am just trying to get it to build so that I know this is a valid approach to the problem and I can get things to work properly.
I was able to get the wrapper to compile after a huge amount of trouble not finiding the qt headers. I finally just copied all the qt headers to the wrappers folder and changed the <>'s to ""'s for included files. (It didn't matter that I added the qt include folder to the project's settings; it still wouldn't find them).
The problem now is that I cannot get the wrapper to link to the core. I have a huge list of linker errors that keep asking for qt objects even though my wrapper doesn't access them directly. It only calls four functions from the wrapped class (so far):
namespace GeoSimCoreDOTNET { public ref class GeoSimulation { public: GeoSimulation(); ~GeoSimulation(); void Continue(); void NextStage(); void NextSimulation(); void Finish(); private: GeoSimulator* mTargetSimulator; }; } // from cpp file: void GeoSimulation::Continue() { mTargetSimulator->Continue(); } void GeoSimulation::NextStage() { mTargetSimulator->NextStage(); } void GeoSimulation::NextSimulation() { mTargetSimulator->NextSimulation(); } void GeoSimulation::Finish() { mTargetSimulator->Finish(); }
As you can see above, none of the Qt classes are directly involved with the wrapper. I think the problem stems from the fact that I am exporting the entire class. (Currently the entire GeoSimulator (a QObject) is marked as a dll export.) If my wrapper only accesses certain methods within the qt based class, should I make the entire class an export or is it possible to just mark the needed methods as exports?
I have done a bit of research and found an article that suggests declaring an interface and exporting the interface rather than the inherited class. Is this a valid approach to resolving these issues?
-
What's the calling convention used by .NET? What are the exact errors? Correct me if I'm wrong but .NET is JIT compiled ... so how are you linking?
public ref class GeoSimulation
This is is not standard C++, I have no idea what it is supposed to do.
If my wrapper only accesses certain methods within the qt based class, should I make the entire class an export or is it possible to just mark the needed methods as exports?
Only referenced symbols must be exported.
Is this a valid approach to resolving these issues?
The issues are not exactly clear. You haven't provided the errors you get. Again, what calling convention is used and can you confirm the ABI between C++ and .NET are compatible in the first place?
Kind regards.
-
The calling convention itself is the same as it is with C++ for the most part (if I am following you correctly). JIT is available but most .NET applications are compiled before distribution. There are only two big differences.
-
.NET programs are compiled into a byte code a lot like a Java program is. However, C++ assemblies that use .NET can also use native conventions and work with conventional memory management. That is why it is often used as a mediator between the two approaches.
-
.NET is memory managed so pointers are considered "unsafe" as the location in memory can changed. This is the reason for the non-standard C++ class designation.
The linking errors are all references to Qt objects and methods that are not used by the wrapper. However, they are part of the class that is exported.
Here is the header for the entire class that is exported to the wrapper...
#pragma once #include "qobject.h" #include "qqueue.h" #include "geosimcore_global.h" #include "ISimulation.h" #include "SimulationData.h" class GEOSIMCORE_EXPORT GeoSimulator : QObject { Q_OBJECT private: ISimulation* mCurrentSimulation; QQueue<ISimulation*> mSimulationQueue; CvMap* mMap; SimulationDataCollection mSimulationData; private /*methods*/: inline void advanceSimulation() { mCurrentSimulation = mSimulationQueue.dequeue(); if (mCurrentSimulation != NULL) { while (mCurrentSimulation->isAutoProvisioning()) { mCurrentSimulation->autoStart(); emit(simulationComplete(mCurrentSimulation)); delete mCurrentSimulation; mCurrentSimulation = mSimulationQueue.dequeue(); } } } signals: void simulationComplete(ISimulation* simulation); void simulatorFinished(); public: GeoSimulator(CvMap* map); ~GeoSimulator(); void addSimulation(ISimulation* simulation); template<typename T> T& createSimulationDataField(const QString& name) { SimulationDataField<T>* field = new SimulationDataField<T>(name); mSimulationData.add(field); return field->getValidData(); } template<typename T> T& getSimulationDataField(const QString& name) {return *mSimulationData.getData<T>(name); } void Start(); void Continue(); void NextStage(); void NextSimulation(); void Finish(); };
As you can see, the exported class has a lot of Qt related stuff in it that isn't necessary for the wrapper to have access too. (The current wrapper header was in the original post).
Here are some example errors from the output:
1>GeoSiimulation.obj : error LNK2028: unresolved token (0A00059A) "public: static void __cdecl QListData::dispose(struct QListData::Data *)" (?dispose@QListData@@$$FSAXPAUData@1@@Z) referenced in function "private: void __thiscall QList<class ISimulation *>::dealloc(struct QListData::Data *)" (?dealloc@?$QList@PAVISimulation@@@@$$FAAEXPAUData@QListData@@@Z) 1>GeoSiimulation.obj : error LNK2028: unresolved token (0A00066F) "void __cdecl qt_assert_x(char const *,char const *,char const *,int)" (?qt_assert_x@@$$FYAXPBD00H@Z) referenced in function "public: class QList<class ISimulation *>::iterator __thiscall QList<class ISimulation *>::erase(class QList<class ISimulation *>::iterator)" (?erase@?$QList@PAVISimulation@@@@$$FQAE?AViterator@1@V21@@Z) 1>GeoSiimulation.obj : error LNK2028: unresolved token (0A000671) "void __cdecl qt_assert(char const *,char const *,int)" (?qt_assert@@$$FYAXPBD0H@Z) referenced in function "public: class ISimulation * & __thiscall QList<class ISimulation *>::first(void)" (?first@?$QList@PAVISimulation@@@@$$FQAEAAPAVISimulation@@XZ) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: bool __thiscall QListData::isEmpty(void)const " (?isEmpty@QListData@@$$FQBE_NXZ) referenced in function "public: bool __thiscall QList<class ISimulation *>::isEmpty(void)const " (?isEmpty@?$QList@PAVISimulation@@@@$$FQBE_NXZ) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: static struct QListData::Data const QListData::shared_null" (__imp_?shared_null@QListData@@2UData@1@B) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: static void __cdecl QListData::dispose(struct QListData::Data *)" (?dispose@QListData@@$$FSAXPAUData@1@@Z) referenced in function "private: void __thiscall QList<class ISimulation *>::dealloc(struct QListData::Data *)" (?dealloc@?$QList@PAVISimulation@@@@$$FAAEXPAUData@QListData@@@Z) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: void * * __thiscall QListData::begin(void)const " (?begin@QListData@@$$FQBEPAPAXXZ) referenced in function "public: class QList<class ISimulation *>::const_iterator __thiscall QList<class ISimulation *>::constBegin(void)const " (?constBegin@?$QList@PAVISimulation@@@@$$FQBE?AVconst_iterator@1@XZ) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: void * * __thiscall QListData::end(void)const " (?end@QListData@@$$FQBEPAPAXXZ) referenced in function "public: class QList<class ISimulation *>::const_iterator __thiscall QList<class ISimulation *>::constEnd(void)const " (?constEnd@?$QList@PAVISimulation@@@@$$FQBE?AVconst_iterator@1@XZ) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: struct QListData::Data * __thiscall QListData::detach_grow(int *,int)" (?detach_grow@QListData@@$$FQAEPAUData@1@PAHH@Z) referenced in function "private: struct QList<class ISimulation *>::Node * __thiscall QList<class ISimulation *>::detach_helper_grow(int,int)" (?detach_helper_grow@?$QList@PAVISimulation@@@@$$FAAEPAUNode@1@HH@Z) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: void __thiscall QListData::dispose(void)" (?dispose@QListData@@$$FQAEXXZ) referenced in function $LN49 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: struct QListData::Data * __thiscall QListData::detach(int)" (?detach@QListData@@$$FQAEPAUData@1@H@Z) referenced in function "private: void __thiscall QList<class ISimulation *>::detach_helper(int)" (?detach_helper@?$QList@PAVISimulation@@@@$$FAAEXH@Z) 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: void * * __thiscall QListData::append(void)" (?append@QListData@@$$FQAEPAPAXXZ) referenced in function __catch$?append@?$QList@PAVISimulation@@@@$$FQAEXABQAVISimulation@@@Z$2 1>GeoSiimulation.obj : error LNK2019: unresolved external symbol "public: __thiscall QObject::QObject(class QObject *)" (??0QObject@@$$FQAE@PAV0@@Z) referenced in function "public: __thiscall GeoSimulator::GeoSimulator(class CvMap *)" (??0GeoSimulator@@$$FQAE@PAVCvMap@@@Z) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "public: virtual bool __thiscall QObject::event(class QEvent *)" (?event@QObject@@UAE_NPAVQEvent@@@Z) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "public: virtual bool __thiscall QObject::eventFilter(class QObject *,class QEvent *)" (?eventFilter@QObject@@UAE_NPAV1@PAVQEvent@@@Z) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "protected: virtual void __thiscall QObject::timerEvent(class QTimerEvent *)" (?timerEvent@QObject@@MAEXPAVQTimerEvent@@@Z) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "protected: virtual void __thiscall QObject::childEvent(class QChildEvent *)" (?childEvent@QObject@@MAEXPAVQChildEvent@@@Z) 1>GeoSiimulation.obj : error LNK2001: unresolved external symbol "protected: virtual void __thiscall QObject::customEvent(class QEvent *)" (?customEvent@QObject@@MAEXPAVQEvent@@@Z)
-
-
@primem0ver "I finally just copied all the qt headers to the wrappers folder" - you should first fix this issue. Copying Qt headers is not a solution. Now you probably don't link against Qt libs and get those linker errors.
-
I am not able to resolve the include problem. I have tried everything I can think of and looked up on the internet. Qt's Visual Studio plugin itself (or perhaps Visual Studio's way of "including" Qt headers) has issues with finding its own headers. Every time I create a new Qt project in Visual Studio, I have to go to the Qt project settings and switch the version to "another version" (default rather than the specific version since I only have one) and then back before it will find the headers and compile. (This is a problem with the plugin or more likely, VIsual Studio itself).
Basically my copying of the files is a solution that other people have found works. I wondered about it myself but thought it would be worth a try. Perhaps my understanding of linking is limited (I do have a few gaps) but I fail to understand why changing the location of the includes would make a difference as long as I leave the libraries alone.
EDIT: I really don't need direct access to Qt objects in the wrapper. As long as it doesn't hurt performance, I would prefer not to include files that include qt libraries. That is why I am asking about the interface approach.
-
The calling convention itself is the same as it is with C++ for the most part (if I am following you correctly).
C++ uses
thiscall
for methods andcdecl
for (global) functions. The win API usesstdcall
probably for keeping backwards compatibility.JIT is available but most .NET applications are compiled before distribution.
.NET programs are compiled into a byte code a lot like a Java program is.
Java's opcode is interpreted. As far as I understand Microsoft's approach, the idea is that .NET's opcode is natively (JIT) compiled when the application is run. The question was rather if in your case native compilation is used from the very beginning, i.e. skipping the JIT part?
class GEOSIMCORE_EXPORT GeoSimulator : QObject
When deriving from
QObject
do it with the public access specifier. There are some internals that need to be exposed for the meta-object system to work.LNK2019: unresolved external symbol "public: __thiscall QObject::QObject(class QObject *)" (??0QObject@@$FQAE@PAV0@@Z) referenced in function "public: __thiscall GeoSimulator::GeoSimulator(class CvMap *)" (??0GeoSimulator@@$FQAE@PAVCvMap@@@Z)
The errors (this one being a prime example) point to you not linking with the QtCore dll, just as @jsulm said.
I really don't need direct access to Qt objects in the wrapper. As long as it doesn't hurt performance, I would prefer not to include files that include qt libraries. That is why I am asking about the interface approach.
Yes, you can use interfaces instead of working with the objects directly, but as with any indirection you'll always take a performance hit. How much of slow down will depend on the program and use, but I consider an additional virtual table lookup inconsequential for almost all cases.
Kind regards.
-
LOL. I fixed it. Dumb mistake. I thought I had linked to the lib file but I guess I didn't. Instead, I only told it where the file was located (i.e. the folder... not the file).