使用 CMake 将带有 .pro 文件的 Qt 项目转换为项目 - moc 问题
Converting Qt project with a .pro file to project using CMake - Problems with moc
我有一个项目,当我用 QtCreator 的 .pro
文件构建它时编译正常。我正在尝试将其转换为 cmake
,但 运行 成为问题。
使用最少的可重现示例进行编辑:
项目文件夹结构:
src/foo.cpp --> #include "ui_foo.h"
src/bar.cpp --> #include "bar.moc"
inc/foo.h
ui/foo.ui
预期编译流程(使用qmake
时观察到的流程):
(1) moc inc/foo.h -o moc_foo.cpp
(2) moc src/bar.cpp -o bar.moc
(3) uic ui/foo.ui -o ui_foo.h
(4) gcc src/foo.cpp -o foo.o
(5) gcc src/bar.cpp -o bar.o
(6) gcc moc_foo.cpp -o moc_foo.o
(7) ld foo.o bar.o moc_foo.o -o foobar
CMakeLists.txt(1):
set(CMAKE_INCLUDE_CURRENT_DIR ON )
set(CMAKE_AUTOMOC ON )
set(CMAKE_AUTOUIC ON )
add_executable(foobar src/foo.cpp src/bar.cpp)
moc_foo.cpp
未创建(第 1 步和第 6 步被遗漏)并且未添加到第 7 步中的 add_executable
。由于缺少对象,出现对 vtable 的未定义引用。我认为这是因为 foo.cpp 和 foo.h 在不同的文件夹中。
CMakeLists.txt(2):
set(CMAKE_INCLUDE_CURRENT_DIR ON )
qt5_wrap_cpp(foobar_moc inc/foo.h src/bar.cpp)
qt5_wrap_ui (foobar_ui ui/foo.ui)
add_executable(foobar src/foo.cpp src/bar.cpp ${foobar_moc} ${foobar_ui})
在步骤 2 中生成 moc_bar.cpp
而不是 bar.moc
。我收到编译器错误,因为在步骤 5 中找不到 bar.moc
。
最简单的方法是自动添加 AUTOMOC property, and let CMake handle QT mocs
set_target_properties(MyProj PROPERTIES AUTOMOC TRUE)
你试图通过 moc .cpp 文件让你自己的生活变得不必要。
尽管名称 qt5_wrap_cpp
实际上以 头文件 作为参数并调用 moc 来生成匹配的 cpp 文件:
qt5_wrap_cpp(foobar_moc inc/foo.h)
# ${foobar_moc} will contain the name
# of the .cpp file created for foo.h
# which we pass to add_executable()
请注意,通常您还会在某处放置一个 foo.cpp
源文件,它与 moc 生成的 .cpp
一起提供了 class 定义的完整实现在 foo.h
.
现在,如果您真的想在 .cpp
文件上调用 moc,则过程略有不同。这里,由于 class 定义不再在头文件中,因此 moc 生成的源文件无法访问它引用的 class 定义。结果,生成的源文件不能再自行编译。相反,您需要使用 #include
在 class 定义 之后将其拉入原始源文件 ,这不是很好。
更糟糕的是,对于 CMake 调用 moc 命令,生成的源文件仍然需要在某处成为依赖链的一部分,这使得一切变得非常丑陋。
我们将使用 qt5_generate_moc
命令在 bar.cpp
上调用 moc(因为 qt5_wrap_cpp
还会将生成的源添加到构建中,这会中断):
qt5_generate_moc(src/bar.cpp ${CMAKE_BINARY_DIR}/bar.moc)
请注意,我们将生成的文件放在二叉树中,以免生成的文件污染源树。如果您设置了 AUTOGEN_BUILD_DIR
,您可能希望将生成的文件放在那里。为了让 #include
找到生成的文件,我们将二叉树添加到项目的包含目录中:
target_include_directories(foobar PUBLIC ${CMAKE_BINARY_DIR})
最后,我们需要一个构建步骤依赖项的自定义目标。没有这个,moc 命令将永远不会真正被调用:
add_custom_target(moc_dummy DEPENDS ${CMAKE_BINARY_DIR}/bar.moc)
add_dependencies(foobar moc_dummy)
底线:只需将所有 class 定义移动到头文件,然后移动它们。没有缺点,你会省去很多丑陋的样板文件。
我有一个项目,当我用 QtCreator 的 .pro
文件构建它时编译正常。我正在尝试将其转换为 cmake
,但 运行 成为问题。
使用最少的可重现示例进行编辑:
项目文件夹结构:
src/foo.cpp --> #include "ui_foo.h"
src/bar.cpp --> #include "bar.moc"
inc/foo.h
ui/foo.ui
预期编译流程(使用qmake
时观察到的流程):
(1) moc inc/foo.h -o moc_foo.cpp
(2) moc src/bar.cpp -o bar.moc
(3) uic ui/foo.ui -o ui_foo.h
(4) gcc src/foo.cpp -o foo.o
(5) gcc src/bar.cpp -o bar.o
(6) gcc moc_foo.cpp -o moc_foo.o
(7) ld foo.o bar.o moc_foo.o -o foobar
CMakeLists.txt(1):
set(CMAKE_INCLUDE_CURRENT_DIR ON )
set(CMAKE_AUTOMOC ON )
set(CMAKE_AUTOUIC ON )
add_executable(foobar src/foo.cpp src/bar.cpp)
moc_foo.cpp
未创建(第 1 步和第 6 步被遗漏)并且未添加到第 7 步中的 add_executable
。由于缺少对象,出现对 vtable 的未定义引用。我认为这是因为 foo.cpp 和 foo.h 在不同的文件夹中。
CMakeLists.txt(2):
set(CMAKE_INCLUDE_CURRENT_DIR ON )
qt5_wrap_cpp(foobar_moc inc/foo.h src/bar.cpp)
qt5_wrap_ui (foobar_ui ui/foo.ui)
add_executable(foobar src/foo.cpp src/bar.cpp ${foobar_moc} ${foobar_ui})
在步骤 2 中生成 moc_bar.cpp
而不是 bar.moc
。我收到编译器错误,因为在步骤 5 中找不到 bar.moc
。
最简单的方法是自动添加 AUTOMOC property, and let CMake handle QT mocs
set_target_properties(MyProj PROPERTIES AUTOMOC TRUE)
你试图通过 moc .cpp 文件让你自己的生活变得不必要。
尽管名称 qt5_wrap_cpp
实际上以 头文件 作为参数并调用 moc 来生成匹配的 cpp 文件:
qt5_wrap_cpp(foobar_moc inc/foo.h)
# ${foobar_moc} will contain the name
# of the .cpp file created for foo.h
# which we pass to add_executable()
请注意,通常您还会在某处放置一个 foo.cpp
源文件,它与 moc 生成的 .cpp
一起提供了 class 定义的完整实现在 foo.h
.
现在,如果您真的想在 .cpp
文件上调用 moc,则过程略有不同。这里,由于 class 定义不再在头文件中,因此 moc 生成的源文件无法访问它引用的 class 定义。结果,生成的源文件不能再自行编译。相反,您需要使用 #include
在 class 定义 之后将其拉入原始源文件 ,这不是很好。
更糟糕的是,对于 CMake 调用 moc 命令,生成的源文件仍然需要在某处成为依赖链的一部分,这使得一切变得非常丑陋。
我们将使用 qt5_generate_moc
命令在 bar.cpp
上调用 moc(因为 qt5_wrap_cpp
还会将生成的源添加到构建中,这会中断):
qt5_generate_moc(src/bar.cpp ${CMAKE_BINARY_DIR}/bar.moc)
请注意,我们将生成的文件放在二叉树中,以免生成的文件污染源树。如果您设置了 AUTOGEN_BUILD_DIR
,您可能希望将生成的文件放在那里。为了让 #include
找到生成的文件,我们将二叉树添加到项目的包含目录中:
target_include_directories(foobar PUBLIC ${CMAKE_BINARY_DIR})
最后,我们需要一个构建步骤依赖项的自定义目标。没有这个,moc 命令将永远不会真正被调用:
add_custom_target(moc_dummy DEPENDS ${CMAKE_BINARY_DIR}/bar.moc)
add_dependencies(foobar moc_dummy)
底线:只需将所有 class 定义移动到头文件,然后移动它们。没有缺点,你会省去很多丑陋的样板文件。