这一节我们就一起来看看如何用CMake来链接自己写的lib库,如何进行这些库文件的管理。
一个团队共同开发软件时,一般都是分模块进行作业的,每个人负责整个软件中的一部分,然后再整合成一个完整的软件系统。具体的做法一般是某个人开发的东西是以链接库的形式供团队中的其他人进行调用,或者供本人负责的程序的其他模块进行调用。
比如,A童鞋开发了一种算法,能做数A与数B的加法运算,A童鞋把它编译成lib库的形式给B童鞋调用,提供给B童鞋的就只有该加法运算的头文件(让B童鞋知道这个函数的接口是怎么样的)以及相应的函数实现lib库文件,B童鞋拿到了这两个文件以后,就可以在自己的程序里面直接调用A童鞋的加法运算。
当涉及到大型的软件开发项目时,这种链接库的形式普遍存在,文件少则几十上百,多则成千上万,这个时候就需要一个工具来对这些链接库进行管理,个人觉得CMake就是一个最好的选择。有用过VTK,ITK等开源工具包的童鞋应该都知道,VTK等编译完了以后会产生好多lib文件,比如vtkCommon.lib, vtkFiltering.lib, vtkGenericFiltering.lib, vtkGraphics.lib, vtkHybrid.lib, vtkImaging.lib, vtkIO.lib……等,而且好多刚学VTK的童鞋在编译链接VTK工程时会经常碰到类似“***无法解析的外部命令”,“***.h找不到”等等这样的错误。如果你是用CMake来构建工程的话,相信这些问题都是小菜一碟而已,所以了解一点CMake的知识对你使用那些开源工具包是灰常有帮助的。
不扯那么多了,进入主题吧。这一节我们就一起来看看如何用CMake来链接自己写的lib库,如何进行这些库文件的管理。
跟《一起学习CMake - 02》一样,在CMake-Study目录下再建一个空的文件夹,就叫HelloCMake03吧(在我机子上完整路径是:D:\CMake\CMake-Study\HelloCMake3),然后把HelloCMake02里的文件都copy到这个新建文件夹里去,等下我们就在这个基础上进行更改。接着在HelloCMake03目录下再建一个新的文件夹,等下里面会存放我们自己要实现lib的文件,简单起见,我们就做一个加法运算可以了,文件夹就起名:AddFunction。接着在AddFunction里新建两个文件,分别是AddFunction.h和AddFunction.cpp,什么作用应该不用再解释了。
AddFunction.h的代码如下:
//做整数的加法运算
int AddFunction(int x, int y);
AddFunction.cpp里的代码如下:
#include <iostream>
int AddFunction(int x, int y)
{
std::cout<<x<<" + "<<y<<" = "<<x+y<<std::endl;
return x + y;
}
然后在AddFunction文件夹里再建立一个CMakeLists.txt文件,CMake原则上要求每个文件夹里都要有一个名叫CMakeLists.txt的文件,因为我们等下是要把AddFunction.h和AddFunction.cpp这两个文件生成一个lib库文件,所以我们必须告诉CMake这个事情,而CMake是只认CMakeLists.txt文件的,所以这个目录里应该要有这个文件存在,里面的内容如下:
add_library(AddFunction AddFunction.cpp)
就这么一行代码。表示什么意思?add_library命令与add_executable命令(在第01节里有介绍)其实是差不多的,前者就是生成lib库文件,后者就是生成exe文件;它们所带的参数也都是一样的,就是用这个参数列表里的源文件来生成这些lib或exe文件。
这样就完成了自己的lib库文件创建的一些工程。接着回到AddFunction的外层目录里,里面有HelloCMake.cpp; HelloCMakeConfig.h.in; CMakeLists.txt这三个文件,先打开CMakeLists.txt文件吧,把里面的代码改为:
cmake_minimum_required(VERSION 2.6)
project(HelloCmake)
# 在CMake里设置HelloCMake软件的主版本号为1,次版本号为0。
set ( HelloCMake_VERSION_MAJOR 1 )
set ( HelloCMake_VERSION_MINOR 0 )
# 是否要使用我们自己的lib库里的加法函数。默认是使用。
option(USE_AddFunction "Use our Add Function" ON)
configure_file(
"${PROJECT_SOURCE_DIR}/HelloCMakeConfig.h.in"
"${PROJECT_BINARY_DIR}/HelloCMakeConfig.h"
)
Include_directories ("${PROJECT_BINARY_DIR}")
# 是否加载AddFunction库文件?
if (USE_AddFunction)
include_directories ("${PROJECT_SOURCE_DIR}/AddFunction")
add_subdirectory (AddFunction)
set (EXTRA_LIBS ${EXTRA_LIBS} AddFunction)
endif (USE_AddFunction)
add_executable(HelloCMake hellocmake.cpp)
target_link_libraries (HelloCMake ${EXTRA_LIBS})
红色字体标注的是新加的代码,一起来看看这些代码作用是什么。首先是option一行代码,option也是CMake里的命令,它的作用就是在CMake GUI上增加一个选项(如图(1)所示),具体到这个例子就是增加选项”USE_AddFunction”;第二个参数”User our Add Function”是标注信息,也就是当你的鼠标停留在CMake GUI的”USE_AddFunction”选项上是会有提示信息出现;第三个参数就是这个选项的值,默认是ON,也就是使用我们自己的加法库。如果更改了这些值,然后用CMake进行Configure, Generate时,这些选项的值会保存在你在”where to build the binaries”指定的编译目录里的CMakeCache.txt文件里。当你再次打开CMake时,CMake会自动去读取CMakeCache.txt文件里的各个选项的值。
(1)
再看看if/endif语句块,它的作用就是根据用户的选择(即USE_AddFunction的值)来决定是否要包含子目录AddFunction(include_directories/add_subdirectory两行代码)到头文件的搜索路径中去;以及设置变量EXTRA_LIBS的值为AddFunction.lib(set一行代码)。Set命令是CMake里用于设置变量值的一个命令,使用频率灰常高。还有,if/endif语句块必须要成对出现,if和endif后面所带的参数也必须一致。
target_link_libraries命令也是用得灰常多的一个命令,它的作用就是把${EXTRA_LIBS}这个变量里的库文件链接到HelloCMake这个工程里去。${……}是取某个变量的值的意思。
最外层的CMakeLists.txt内容介绍完,接着看看HelloCMakeConfig.h.in里要添加什么东西?在该文件的最后加入如下代码:
#cmakedefine USE_AddFunction
这行代码是告诉CMake在生成HelloCMakeConfig.h文件时用”#define USE_AddFunction”或者”/*#defineUSE_AddFunction*/”来代替” #cmakedefine USE_AddFunction”,到底是前者还是后者,取决于USE_AddFunction选项的值(ON还是OFF)。编译完HelloCMake这个工程以后,打开HelloCMakeConfig.h看看就知道怎么回事了。
接着来看看HelloCMake.cpp文件,完整代码如下:
#include <iostream>
#include "HelloCMakeConfig.h"
#ifdef USE_AddFunction
#include "AddFunction.h"
#endif
int main(int argc, char *argv[])
{
std::cout<<"HelloCMake软件的主版本号是:"
<< HelloCMake_VERSION_MAJOR << std::endl;
std::cout<<"HelloCMake软件的次版本号是:"
<< HelloCMake_VERSION_MINOR << std::endl;
fprintf(stdout, "%s Version is: %d.%d\n",
argv[0],
HelloCMake_VERSION_MAJOR,
HelloCMake_VERSION_MINOR);
std::cout<<"Study CMake Together - HelloCMake2"<<std::endl;
int a, b;
std::cin>>a>>b;
#ifdef USE_AddFunction
int addResult = AddFunction(a,b);
#else
int addResult = a + b;
#endif
return 0;
}
增加的代码都粗体字显示,这些代码都比较简单,一看就能明白了,这里就不多作介绍。有了这些文件以后,走一遍CMake(Configure, Generate),整个工程也就构建完成了。
我们来看看到底发生了哪些变量,有图有真相,看图吧:
(2)
(3)
(4)
知道了这些东西,以后你在使用VTK, ITK等工具包时,再碰到类似前文提到的错误时,也就知道怎么回事了吧?下一节我们结合VTK等工具包来看看怎么链接VTK里的库文件到自己的工程里去。
如果有不对的地方,请告诉我(水灵 MSN:[email protected] QQ:348774226)。