导言
我想在开始C++工程实践系列文章之前,先说明一下为什么想要更新这个系列。
这个想法主要是因为我个人的C++学习历程,在上学期间我很难接触真实的工程项目,仅仅停留在语言应用的层面上,这在我毕业找工作时不会是我的加分项,并且在我正式进入公司之后也要花更多的时间去适应公司大型项目的开发,所以我想更新这个系列以帮助想要提高C++层次的同学可以更快的熟悉真正的C++项目是什么样的,学习到更多的C++工程实践经验,也希望通过这个窗口和大家一起学习,讨论C++。
开始
废话说完让我们直接进入正题~
首先,此系列面向的是有一定C++基础的同学,默认语言层面的知识大家都会了,所以不会上来就贴代码教大家怎么写代码,那么既然是C++工程实践,首先教大家如何在工程中集成第三方库,因为在真正的工程项目中往往需要集成一些三方库来实现一些功能,又不是自己重复造轮子。这里的三方库就使用功能非常强大也是我工作中经常使用的opencv图像处理库,如果不了解opencv库的话可以只了解如何集成一个三方库就好,方法都是类似的。
选择IDE
不知道大家都是选择什么IDE来开发C++项目,我这里推荐大家使用Clion进行C++开发,主要是因为它的智能提示以及UI比较好看(颜值党狂喜~),而且学生党可以申请免费试用~但是考虑到大家可能目前使用clion的人数不多,所以暂时会使用vscode进行工程演示,同时vscode也是一款非常强大的编辑器!可以用来开发各种语言,并且开源且跨平台。
目录结构
虽然第一章只会讲解集成opencv以及编写简单的测试代码,但是这个工程还是要搭建的有模有样的,之后的项目也会在此工程的基础上开发(虽然需求暂时还没想好~),肯定也会使用到opencv库。
- 创建工程目录
首先打开vscode,在我的Windows电脑上如下图所示:
点击启动下方的打开文件夹然后选择一个文件夹作为我们的工作目录(在一个路径下创建一个空目录就好,名字可以随意取,项目可能会与ai相关,所以我目前取名aistar~),打开后vscode界面如下:
点击红色箭头指向的图标可以添加文件或者文件夹,根据工程经验实践目录结构一般如下图所示,如果还需要其他文件夹会后续补充。
- src:存放项目的源代码,工程的所有源代码都会存放在此目录下
- test:单元测试目录,一般会存放项目所有的单元测试用例(后续会使用Catch2进行单元测试)
- third_party:存放项目引用的第三方库,这里等会会将opencv库放进去~
- tutorial:一般会将工程实例代码存放在此目录下,也就是我们常写的main函数~
- CmakeLists.txt: 顶层cmake文件,负责整体项目的构建流程
- 编写cmake以及示例代码
顶层cmake关键代码:
cmake_minimum_required(VERSION 3.10)
set(CMAKE_BUILD_TYPE Release CACHE STRING "build type")
project(
aistar
VERSION 0.0.1
LANGUAGES C CXX
)
set(INC_DIR ${PROJECT_SOURCE_DIR}/_include CACHE PATH "aistar include")
set(LIB_DIR ${PROJECT_SOURCE_DIR}/_lib CACHE PATH "aistar lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIB_DIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR})
set(CMAKE_INSTALL_LIBDIR ${LIB_DIR}/${CMAKE_BUILD_TYPE})
add_custom_target(
LINK_HEADERS ALL
COMMENT "link headers..."
)
include(CMakeLists_Headers.txt)
macro(makeLink src dest target)
add_custom_command(
TARGET ${target} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest}
DEPENDS ${dest}
)
endmacro()
add_custom_command(
TARGET LINK_HEADERS PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${INC_DIR}/${PROJECT_NAME}
)
foreach(header_file ${INCLUDE_HEADERS})
string(REPLACE "/" ";" arr ${header_file})
list(GET arr -1 file_name)
makeLink(${PROJECT_SOURCE_DIR}/${header_file} ${INC_DIR}/${PROJECT_NAME}/${file_name} LINK_HEADERS)
endforeach()
if (WIN32)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /std:c++17 -O2")
else ()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++11 -fno-exceptions -Wno-invalid-offsetof")
endif ()
add_subdirectory(third_party)
add_subdirectory(src)
install(
FILES ${INCLUDE_HEADERS}
DESTINATION ${INC_DIR}/${PROJECT_NAME}
COMPONENT devel
)
需要说明的点是在工程目录下新建了一个CMakeLists_Headers.txt文件用于存放工程需要对外暴露的头文件,并且结合cmake在每次构建项目时更新头文件,以下是CMakeLists_Headers.txt文件的内容:
cmake_minimum_required(VERSION 3.10)
set(INCLUDE_HEADERS
src/util/show_image.h
)
我们这篇文章只讲解如何集成opencv以及简单的使用,所以我们在src目录下新建一个util目录,并且实现show_image函数实现打开图片,转换为灰度图并显示图片,所以我们需要在src目录下新建CmakeLists.txt文件用于构建我们的库,cmake脚本如下:
cmake_minimum_required(VERSION 3.10)
include_directories(${PROJECT_SOURCE_DIR}/src/include)
add_subdirectory(util)
set(STATIC_LIB_NAME ${PROJECT_NAME}-static)
set(SHARED_LIB_NAME ${PROJECT_NAME}-shared)
add_library(
${STATIC_LIB_NAME} STATIC
$<TARGET_OBJECTS:util>
)
add_library(
${SHARED_LIB_NAME} SHARED
$<TARGET_OBJECTS:util>
)
set_target_properties(${STATIC_LIB_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(${SHARED_LIB_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} VERSION ${PROJECT_VERSION})
util目录下的cmake脚本如下:
cmake_minimum_required(VERSION 3.10)
project(util)
set(SRC
show_image.cc
)
add_library(${PROJECT_NAME} OBJECT ${SRC})
同时在util目录下新建show_image.h和show_image.cc文件
show_image.h:
#ifndef SHOWIMAGE_H
#define SHOWIMAGE_H
#include <string>
namespace aistar{
void __declspec(dllexport) show_image(const std::string& image_path);
}
#endif
show_image.cc:
#include "show_image.h"
namespace aistar{
void show_image(const std::string& image_path)
{
return;
}
}
由于我们暂时还没有集成opencv,所以暂时空实现就好~
最后我们在third_party目录下新建一个空的CmakeLists.txt文件就可以愉快的cmkae了~
虽然我的电脑是Windows,但是Windows的WSL2可以很好的支持Linux系统(我这里使用的是Ubuntu的22.04版本),所以我给大家演示Windows和Linux两个平台的编译。
Windows
首先点击vscode导航栏的终端 -> 新建终端,此时会在当前目录打开默认的powershell如下图所示:
- 输入命令 mkdir build_win
- 输入命令 cd build_win
- 输入命令 cmake ..
- 输入命令 cmake –build .
需要注意的是输入cmake –build .命令构建时一般默认为Debug模式,如果想生成Release模式的库可以在命令后面加上–config Release,生成的库文件会输出在工程目录的_lib目录下,此路径可以根据顶层cmake脚本指定。工程后续会新建tools文件夹以存放各平台编译脚本而不是在命令行一行一行的敲命令哦~
Linux
如果你的Windows电脑安装了WSL2,那么终端的加号旁边的展开按钮可以选择你的Linux环境并在当前路径打开其终端,如下图所示:
- 输入命令 mkdir build_linux
- 输入命令 cd build_linux
- 输入命令 cmake ..
- 输入命令 make -j8
- 集成opencv并实现show_image函数
首先下载Windows平台以及Linux平台的opencv库并将其放置在third_party目录下(搜索opencv在其官网即可下载,也可以下载源码自己编译~如果不会下载或者不想下载的可以找我要~)如下图所示:
注意使用不同的文件夹名称区分,然后就需要填充CmakeLists.txt文件啦~(后续会将opencv等三方库的集成封装为一个单独的.cmake文件)
set(THIRD_PARTY_PATH "${PROJECT_SOURCE_DIR}/third_party")
if(WIN32)
set(OPENCV_DIR ${THIRD_PARTY_PATH}/opencv_win)
elseif(UNIX)
set(OPENCV_DIR ${THIRD_PARTY_PATH}/opencv_linux/lib/cmake/opencv4)
endif()
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR} NO_DEFAULT_PATH)
include_directories(${OpenCV_INCLUDE_DIRS})
还需要在src目录下的CmkaeLists.txt文件里链接opencv库即可,在文件最后添加如下语句:
target_link_libraries(${SHARED_LIB_NAME} ${OpenCV_LIBS})
target_link_libraries(${STATIC_LIB_NAME} ${OpenCV_LIBS})
然后就可以开始愉快的编译了~参考第二步的编译过程即可,编译成功之后我们引入opencv的头文件与函数再编译试试看有没有成功,打开show_image.cc文件,修改代码如下:
#include "show_image.h"
#include "opencv2/opencv.hpp"
namespace aistar{
void show_image(const std::string& image_path)
{
if (image_path.empty())
{
return;
}
auto image = cv::imread(image_path, cv::IMREAD_UNCHANGED);
if (image.empty())
{
return;
}
int code = -1;
if (image.channels() == 4)
{
code = cv::COLOR_BGRA2GRAY;
} else if (image.channels() == 3)
{
code = cv::COLOR_BGR2GRAY;
}
if (code > 0)
{
cv::cvtColor(image, image, code);
}
cv::namedWindow("gray image", cv::WINDOW_NORMAL);
cv::imshow("gray image", image);
cv::waitKey();
return;
}
}
该函数目前实现了一个简单的将图像转换为灰度图并显示的功能,编译成功之后可以看到我们的编译产物如下:
至此我们已经成功集成opencv并实现show_image函数啦~
- 编写测试代码测试库的使用
在tutorial目录下新建一个tutorial.cc文件与CmakeLists.txt文件后使用cmake编译测试项目并链接我们的库:
cmake_minimum_required(VERSION 3.10)
set(tutorial_name "tutorial")
set(CMAKE_BUILD_TYPE Release CACHE STRING "build type")
project(${tutorial_name} LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
include_directories(${AISTAR_DIR}/_include/aistar)
add_library(aistar SHARED IMPORTED)
set_target_properties(aistar PROPERTIES IMPORTED_IMPLIB ${AISTAR_DIR}/_lib/Release/aistar.lib)
set(OPENCV_DIR ${AISTAR_DIR}/third_party/opencv_win)
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR} NO_DEFAULT_PATH)
set(Application_SOURCES tutorial.cc)
add_executable(${tutorial_name} ${Application_SOURCES})
target_link_libraries(${tutorial_name} aistar ${OpenCV_LIBS})
这里需要注意在cmake编译时我们需要指定AISTAR_DIR变量来说明我们库的位置一般通过绝对路径指定~
tutorial.cc测试代码如下:
#include <iostream>
#include "show_image.h"
using namespace aistar;
int main()
{
show_image("xxx.png");
}
然后就编译并运行生成的exe文件就会显示输入的图片转换后的灰度图了~(运行失败的话可能需要将opencv的动态库放置在与exe的相同路径下哦~)
总结
这章完成了工程的初步搭建以及集成opencv并使用,虽然留了一些坑,但是这才是真实的C++工程实践!就是一个慢慢完善的过程,下一章主要会完善工程结构,补充编译脚本以及确立需求~如果大家有任何想法或者建议欢迎沟通~(大家的点赞与评论是我继续更新的动力,谢谢大家!)