I have a rather simple top-level CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(Api)
add_subdirectory(Cli)
containing 2 subdirectories Api and Cli.
The API CMakeLists.txt is rather simple also:
cmake_minimum_required(VERSION 3.14)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)
add_library(Api
api.h
api.cpp
API_global.h)
target_link_libraries(Api Qt${QT_VERSION_MAJOR}::Core)
add_compile_definitions(API_LIBRARY)
The sources are trivial:
API_global.h
#ifndef API_GLOBAL_H
#define API_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(API_LIBRARY)
#define API_EXPORT Q_DECL_EXPORT
#else
#define API_EXPORT Q_DECL_IMPORT
#endif
#endif // API_GLOBAL_H
API.h
#ifndef API_H
#define API_H
#include "API_global.h"
class API_EXPORT API
{
public:
API() = default;
void printHello() const;
};
#endif // API_H
api.cpp
#include "api.h"
void API::printHello() const {}
The CLI subproject CMakeLists.txt then simply calls
find_library(API Api HINTS ${CMAKE_BINARY_DIR}/Api)
include_directories(${PROJECT_SOURCE_DIR}/Api)
target_link_libraries(cli PUBLIC ${API})
Within main.cpp a call to printHello fails with
main.cpp.obj:-1: error: LNK2019: unresolved external symbol "__declspec(dllimport) public: void __cdecl API::printHello(void)const " (__imp_?printHello#API##QEBAXXZ) referenced in function main
I first tested this on Mac where all is good. Fiddling around with this setup on Windows the entire morning but I cannot get my head around it. The find_library is returning the Api.lib as expected. My first guess would be that the API_LIBRARY is still defined in the Cli subproject but this is not the case apparently.
I must be missing something very obvious!
Related
I am trying to build a simple QT6 program using VSCode and MSVC. For this, I have following files:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10.0)
project(test VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(test main.cpp mainwindow.cpp mainwindow.h)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
target_link_libraries(test Qt6::Core Qt6::Widgets)
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow()
: QMainWindow()
{
}
MainWindow::~MainWindow()
{
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
virtual ~MainWindow();
};
#endif // MAINWINDOW_H
I am using MSCV with Kit Visual Studio Community 2019 Release - x86_amd64.
My QT6 install is under C:\Qt and my Path varable contains C:\Qt\6.2.3\msvc2019_64
As I stated in the title, when I set Q_OBJECT for the MainWindow class, I get the following linker errors:
[build] mainwindow.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""public: virtual struct QMetaObject const * __cdecl MainWindow::metaObject(void)const " (?metaObject#MainWindow##UEBAPEBUQMetaObject##XZ)". [C:\Users\Nightbird\Desktop\test\build\test.vcxproj]
[build] mainwindow.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""public: virtual void * __cdecl MainWindow::qt_metacast(char const *)" (?qt_metacast#MainWindow##UEAAPEAXPEBD#Z)". [C:\Users\Nightbird\Desktop\test\build\test.vcxproj]
[build] mainwindow.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""public: virtual int __cdecl MainWindow::qt_metacall(enum QMetaObject::Call,int,void * *)" (?qt_metacall#MainWindow##UEAAHW4Call#QMetaObject##HPEAPEAX#Z)". [C:\Users\Nightbird\Desktop\test\build\test.vcxproj]
If I remove Q_OBJECT, the project compiles / links and runs. The problem is, for me to later implement Signal/Slot-Mechanisms, I need Q_OBJECT.
Now the linker errors suggest that something goes wrong after MOC creation, however I cant find the mistake I made. Do I have to change something in my CMakeList.txt?
So it seems I found out what the problem was...
I rearranged my CMakeList.txt like so:
cmake_minimum_required(VERSION 3.10.0)
project(test VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
add_executable(test main.cpp mainwindow.cpp mainwindow.h)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
target_link_libraries(test Qt6::Core Qt6::Widgets)
Notice the position of the set()-Commands... That seemed to be it.
Now the output also shows [build] Automatic MOC and UIC for target test ... that didn't show before. So if you're struggling with linker errors regarding MOC, see if this line shows up. If it doesn't, check the order of cmake commands
I have here a class called engine and im trying to use, but when i include it i get LNK2019 error. Im running Visual Studio 2019 x86_64 compiler. Any ideas what could be wrong?
I have constructor and destructor defined in cpp file.
#pragma once
namespace NRD {
const int SCREEN_WIDTH(1280);
const int SCREEN_HEIGHT(720);
class Engine {
public:
Engine();
Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete;
~Engine();
static Engine& Ref() {
static Engine reference;
return reference;
}
};
static Engine& Core = Engine::Ref();
}
Here is my cpp file:
#include "nrdengine/engine.h"
#include "nrdengine/service_locator.h"
#include "platform/glfw_window.h"
#include <iostream>
namespace NRD {
Engine::Engine() {
std::cout << "Initializing window!" << std::endl;
ServiceLocator::Provide(new CustomWindow());
}
Engine::~Engine() {
ServiceLocator::GetWindow()->DestroyWindow();
}
}
[build] main.obj : error LNK2019: unresolved external symbol "public: __cdecl NRD::Engine::Engine(void)" (??0Engine#NRD##QEAA#XZ) referenced in function "public: static class NRD::Engine & __cdecl NRD::Engine::Ref(void)" (?Ref#Engine#NRD##SAAEAV12#XZ) [C:\Users\Dawid\Desktop\NRD-Engine\build\nrdengine_cpp_application.vcxproj]
[build] main.obj : error LNK2019: unresolved external symbol "public: __cdecl NRD::Engine::~Engine(void)" (??1Engine#NRD##QEAA#XZ) referenced in function "void __cdecl `public: static class Engine::Ref & __cdecl NRD::Engine::Ref(void)'::`2'::`dynamic atexit destructor for 'reference''(void)" (??__Freference#?1??Ref#Engine#NRD##SAAEAV12#XZ#YAXXZ) [C:\Users\Dawid\Desktop\NRD-Engine\build\nrdengine_cpp_application.vcxproj]
[build] C:\Users\Dawid\Desktop\NRD-Engine\build\Debug\nrdengine_cpp_application.exe : fatal error LNK1120: 2 unresolved externals [C:\Users\Dawid\Desktop\NRD-Engine\build\nrdengine_cpp_application.vcxproj]
I have two CMakeList files (one for Engine project, one for App)
APP:
cmake_minimum_required(VERSION 3.5)
project(nrdengine_cpp_application VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
add_subdirectory(external/engine)
file(GLOB_RECURSE SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/*.c
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
#nrdengine_cpp_application.exe
add_executable(nrdengine_cpp_application ${SOURCE_FILES})
target_link_libraries(nrdengine_cpp_application PUBLIC nrdengine_cpp_engine)
target_include_directories(nrdengine_cpp_application PUBLIC nrdengine_cpp_engine)
ENGINE:
cmake_minimum_required(VERSION 3.5)
project(nrdengine_cpp_engine VERSION 0.0.1 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
#add external libs
#find_package(OpenGL REQUIRED)
# install python and Jinja2
set(GLAD_SOURCES_DIR "${PROJECT_SOURCE_DIR}/external/glad")
add_subdirectory("${GLAD_SOURCES_DIR}/cmake" glad_cmake)
add_subdirectory(external/glfw)
add_subdirectory(external/glm)
add_subdirectory(external/assimp)
file(GLOB_RECURSE SOURCE_FILES
${CMAKE_SOURCE_DIR}/src/*.c
${CMAKE_SOURCE_DIR}/src/*.cpp)
glad_add_library(glad_gl_core_mx_31 REPRODUCIBLE MX API gl:core=3.1)
add_library(nrdengine_cpp_engine ${SOURCE_FILES})
target_include_directories(nrdengine_cpp_engine
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
glm
PRIVATE
glfw
assimp
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
glm
PRIVATE
glad_gl_core_mx_31
glfw
assimp
)```
I'm kinda guessing since your question is kinda hard to answer since it could be a lot of things. But here is my inference.
It's not recommended to glob your source files like you are doing here.
file(GLOB_RECURSE SOURCE_FILES
${CMAKE_SOURCE_DIR}/src/*.c
${CMAKE_SOURCE_DIR}/src/*.cpp)
NOTE: My suggestion requires 3.13 or higher version of CMake.
This causes lots of issues and might be the cause of your problems. Please see this post by one of the CMake maintainers on how to add source files to your project.
https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/
TLDR:
Make a CMakeLists.txt in your src/ directory like so.
target_sources(foobar PRIVATE
engine.cpp
engine.h
)
And make sure to call add_subdirectory(src) to process that new CMakeLists.txt
If you are interested in why globbing in build systems in general is bad. I can provide links.
I am working on the project which uses Qt Remote Objects and I wanted to test one of my components using auto-generated ReplicaDefSimpleSource class (see the example below for the details). However, when I tried to link test executable with my library, I got the following linker error:
main.obj : error LNK2001: unresolved external symbol "public: static struct QMetaObject const ReplicaDefSimpleSource::staticMetaObject" (?staticMetaObject#ReplicaDefSimpleSource##2UQMetaObject##B)
I checked my MyLib.dll with DependencyWalker, the required symbol is there:
79 (0x004f), 78 (0x0000004e), public: static struct QMetaObject const ReplicaDefSimpleSource::staticMetaObject, 0x00018370, Microsoft
I also checked dumpbin /EXPORTS Debug/MyLib.lib | grep "static struct QMetaObject", and again the required symbol is there:
?staticMetaObject#ReplicaDefSimpleSource##2UQMetaObject##B (public: static struct QMetaObject const ReplicaDefSimpleSource::staticMetaObject)
To build the project I used CMake:
cmake -G "Visual Studio 15 2017 Win64" ../
cmake --build . --config Debug
To reproduce the problem you need the following files:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.11)
project(test_project)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set (CMAKE_CXX_STANDARD 17)
find_package(Qt5 COMPONENTS
Core REQUIRED
RemoteObjects REQUIRED
Test REQUIRED
)
qt5_generate_repc(GENERATED ReplicaDef.rep SOURCE)
qt5_generate_repc(GENERATED ReplicaDef.rep REPLICA)
set_source_files_properties(${GENERATED} PROPERTIES GENERATED TRUE SKIP_AUTOMOC TRUE)
add_library(MyLib SHARED a.cpp ${GENERATED})
set_target_properties(MyLib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_link_libraries(MyLib
Qt5::Core
Qt5::RemoteObjects
)
add_executable(main main.cpp
# moc_rep_ReplicaDef_source.cpp
)
target_link_libraries(main MyLib Qt5::Test)
main.cpp: yep, I know it is not how you are supposed to test Qt code, I just use it as minimal example (e.g. it doesn't have QApplication)
#include "rep_ReplicaDef_source.h"
#include <QTest>
#include <QSignalSpy>
#include <QUrl>
#include <memory>
void foo();
int main()
{
foo(); // here no problem with linker
ReplicaDefSimpleSource source;
QRemoteObjectHost host;
host.setHostUrl(QUrl{ "local:main" });
host.enableRemoting(&source);
// QSignalSpy is using staticMetaObject that linker complains about
QSignalSpy spy(&source, &ReplicaDefSimpleSource::settingsChanged);
spy.wait(1000);
return 0;
}
ReplicaDef.rep:
#include <QtCore>
#include <QString>
class ReplicaDef
{
PROP(QString settings);
};
a.cpp: it actually can be anything, I just put there some dummy function to show that the symbols from a.cpp are visible
#include <iostream>
void foo() { std::cout << "foo" << std::endl; }
As a workaround I can just explicitly include auto-generated sources in my test, but this is not acceptable solution.
add_executable(main main.cpp
moc_rep_ReplicaDef_source.cpp
)
The provided example of course compiles and works on any normal system, I just have the problem on windows, which I am forced to use (sorry for asking a question about this platform).
What should I do to test my system using auto-generated replica classes and without compiling their sources multiple times?
As a side question: why the linker does not complain about multiple definitions if I include the replica sources in main executable?
=== EDIT ===
Just to make sure that nothing weird happens when I compile Qt autogenerated code with my hand-written code, I tried to put replica sources in a separate library:
qt5_generate_repc(GENERATED ReplicaDef.rep SOURCE)
qt5_generate_repc(GENERATED ReplicaDef.rep REPLICA)
set_source_files_properties(${GENERATED} PROPERTIES GENERATED TRUE SKIP_AUTOMOC TRUE)
add_library(MyLib-replica SHARED ${GENERATED})
target_link_libraries(MyLib-replica
Qt5::Core
Qt5::RemoteObjects
)
set_target_properties(MyLib-replica PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_library(MyLib SHARED a.cpp)
set_target_properties(MyLib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_link_libraries(MyLib
MyLib-replica
)
but the problem remains the same. Missing symbols if I do not include autogenerated sources explicitly in each target.
I'm struggling to use a DLL generated using CMAKE and C++. I'm able to build the library, include it and build the target project, the problem is that when I run the target build it crashes immediately.
My code is super easy and I don't know what I'm missing.
The DLL is built using CMAKE in a separate project. Here's the code
DLL PROJECT:
CMakeLists
cmake_minimum_required(VERSION 3.5)
project(LibProj LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions("-DBUILD_LIB")
file(GLOB
INCLUDE_FILES
baselibraryclass.h
)
file(GLOB
SOURCE_FILES
baselibraryclass.cpp
)
add_library(yourlib SHARED ${INCLUDE_FILES} ${SOURCE_FILES} )
baselibraryclass.h
#ifndef BASELIBRARYCLASS_H
#define BASELIBRARYCLASS_H
#ifdef BUILD_LIB
#define EXT_DLL __declspec(dllexport)
#else
#define EXT_DLL __declspec(dllimport)
#endif
#include <string>
class EXT_DLL BaseLibraryClass
{
public:
BaseLibraryClass();
};
#endif // BASELIBRARYCLASS_H
baselibraryclass.cpp
#include "baselibraryclass.h"
#include <iostream>
EXT_DLL BaseLibraryClass::BaseLibraryClass()
{
std::cout << "Hi from the library Class Object " << std::endl;
}
Target project
CMakeLists
cmake_minimum_required(VERSION 3.5)
project(TargetProject LANGUAGES CXX)
#Including the path of the library header
include_directories(D:/TestingDLLNativeCpp/Library/include)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(TargetProject main.cpp)
#The path of the built library is D:/TestingDLLNativeCpp/Library/Debug/yourlib.lib
target_link_libraries(TargetProject PRIVATE D:/TestingDLLNativeCpp/Library/Debug/yourlib.lib)
Target project main.cpp
#include <iostream>
#include <baselibraryclass.h>
using namespace std;
int main()
{
BaseLibraryClass testObk;
return 0;
}
As I wrote above, cmake configures properly and the compiler is able to build for both projects, however the target executables crashes immediately.
What am I doing wrong o.O??
Thanks for the attention
The issue of the dll not being included in the build directory might be solved by setting the CMAKE_RUNTIME_OUTPUT_DIRECTORY. However, a better practice would be to set the output directory of the .dll on a target basis. This will ensure that you have no unwanted side-effects in the long run.
set_target_property(yourlib PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
I have simple program as follow:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(test LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR})
add_executable(test main.cpp)
target_include_directories(test PRIVATE ${PROJECT_SOURCE_DIR})
target_link_libraries(test PRIVATE power.dll)
main.cpp:
#include <iostream>
#include "power.h"
using namespace std;
int main()
{
cout << "Hello World!" << endl;
power(4.);
return 0;
}
power.h:
#ifndef POWER_H
#define POWER_H
double power(double number) noexcept;
#endif // POWER_H
Implementation of power.h is in a .dll named power.dll.
If I compile this project with MinGW 7.3.0 X64 says:
error: undefined reference to `power(double)'
If I compile it with MSVC 2017 X64 says:
error: LNK1104: cannot open file 'power.lib'
both errors show that power.dll can't detect by the linker.
I did many searches but none of solutions worked for me!
Can anyone help about this?
Thanks in advance!
Your modelling of the dynamic library is incorrect, both on CMake and on the source level.
As a starting point, try building the dll as part of the same CMake project as the consuming executable:
cmake_minimum_required(VERSION 3.5)
project(test LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(GenerateExportHeader)
add_library(power SHARED power_sources.cpp power.h)
generate_export_header(power)
target_include_directories(power PUBLIC ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR})
add_executable(test main.cpp)
target_link_libraries(test PRIVATE power)
Note the use of the generate_export_header function, which instructs CMake to generate macros for exporting functions on shared library interfaces in a portable way. Since generated files go to the binary directory tree, we have to adjust the include directories for the library accordingly.
To make sure the function gets properly exported, change your header as follows:
#ifndef POWER_H
#define POWER_H
#include <power_export.h>
POWER_EXPORT double power(double number) noexcept;
#endif // POWER_H
Note that generare_export_header allows you to customize the generated export header extensively.
Be sure you get the project to build and run from this baseline.
If you want to build the dll externally (which is not strictly necessary, but since that's what your question is about...), we have to modify the CMake file to something like:
cmake_minimum_required(VERSION 3.5)
project(test LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(power)
add_executable(test main.cpp)
target_link_libraries(test PRIVATE power)
With all the magic here happening in the find_package call. That call is now responsible for providing all the information that was previously handled by the lines for building the library:
Providing of an imported target power for consumption by the target_link_libraries call
Association of the library name of the import library (the power.lib file) via that imported target
Exposure of the public include directories for both power.h and power_export.h via that imported target
You can either construct such an imported target manually in the find script, or have CMake do it for you. In the first case, create a FindPower.cmake script file, make sure it's location is part of the CMAKE_MODULE_PATH and write the code for finding the library and header files and constructing the imported target in there. Note that getting this right in a portable way can be very tricky and goes far beyond the scope of a StackOverflow question. In the second case, have the CMake script that builds the power library perform an install step during which a config file package will get generated, which can then be consumed by your test project. Note that this approach is not viable if the power library is not itself being built with CMake, so in that case you will have to stick with the first option.
Dynamic linking in Windows requires that externally visible symbols are declared with the keyword __declspec. Your header "power.h" should be modified:
#ifndef POWER_H
#define POWER_H
#if defined(__WIN32__) && !defined(__CYGWIN__)
# if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(BUILD_DLL)
# define POWERAPI __declspec(dllexport)
# elif (defined(_MSC_VER) || defined(__MINGW32__))
# define POWERAPI __declspec(dllimport)
# endif
#endif
POWERAPI double power(double number) noexcept;
#endif // POWER_H
In the project building the DLL power.dll with CMake, you should define the symbol BUILD_DLL:
add_definitions(-DBUILD_DLL)
then it should generate a power.lib file when the MSVC compiler and a power.a when using MINGW. Don't define BUILD_DLL in the project using the DLL, and it should work.