在完成接口开发后,为了给其他开发人员调用,通常需要将这部分代码打包生成动态链接库文件。
动态链接库很好地保证了代码的封装性和独立性,作为接口,它可以很好地独立于主程序,便于更新。调用接口的主程序编译时也会绕过已生成的动态链接库,仅作连接,在运行时使用它,减少了前端人员编译的时间消耗。
同时由于动态链接库很难被反编译,只有头文件是可见的,这也同时保证了代码的安全性。
在Windows系统上,以.dll为后缀的是动态链接库,以.a结尾的是静态链接库。
静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。
*Linux系统上的动态链接库后缀为.so,静态链接库后缀还是.a。本篇不做过多介绍。
│ CMakeLists.txt
│
├─build
│
├─examples
│ test_api.cpp
│
├─include
│ └─detectionapi
│ DetectionApi.h
│
├─lib
│ libopencv_calib3d450.dll
│ libopencv_core450.dll
│
└─src
DetectionApi.cpp
首先,为保证目录结构的干净,先将待编译的文件整理为上述目录结构。
上述目录中,include存放头文件(.h, .hpp),src存放源文件(.c, .cpp),examples存放测试代码,lib存放各种支持程序运行的链接库。CMakeLists.txt放在根目录下,将编译生成的内容全部放在build目录下。
然后开始编辑CMakeLists.txt文件,一个样例如下所示。
# 最低CMake版本要求
cmake_minimum_required(VERSION 3.9.0)
# 项目名称,编译好dll或exe的名称
project(detection)
# 设置C/C++标准
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 头文件路径
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/detectionapi)
include_directories(../opencv/build/include)
include_directories(../opencv/build/include/opencv2)
# 枚举头文件
file(GLOB_RECURSE INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include/detectionapi/*.h)
# 指定引用的外部库的搜索路径
LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 枚举源文件
file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# 链接
set(LINK_LIBS
libopencv_calib3d450.dll
libopencv_core450.dll
)
# 生成dll库
ADD_LIBRARY(detectionapi SHARED ${INCLUDES} ${SOURCES})
TARGET_LINK_LIBRARIES(detectionapi ${LINK_LIBS})
INSTALL(TARGETS detectionapi DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 设置目标属性
SET_TARGET_PROPERTIES(detectionapi PROPERTIES LINKER_LANGUAGE C
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
OUTPUT_NAME "DetectionApi"
PREFIX "")
# 生成可执行文件,仅用作测试
add_executable(detection examples/test_api.cpp ${INCLUDES})
TARGET_LINK_LIBRARIES(detection ${LINK_LIBS})
TARGET_LINK_LIBRARIES(detection ${PROJECT_BINARY_DIR}/lib/libDetectionApi.dll.a)
如上述样例所示,一个标准的CMake文件包含了以下几大部分:
上述示例,基础配置和输入配置是比较显而易见的,这里着重说一下输出配置。
ADD_LIBRARY(detectionapi SHARED ${INCLUDES} ${SOURCES})
声明这是一个lib库,第一个参数是链接库属性,第二个是类型(SHARED表示动态链接库),第三个参数是输入参数中的头文件和源文件。
TARGET_LINK_LIBRARIES(detectionapi ${LINK_LIBS})
这个是链接的第三方库,如自行编译出的opencv的各种dll等。
INSTALL(TARGETS detectionapi DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib)
dll的实际安装指令,按照上述写法将被安装在/lib目录下。
SET_TARGET_PROPERTIES(detectionapi PROPERTIES LINKER_LANGUAGE C
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
OUTPUT_NAME "DetectionApi"
PREFIX "")
设置dll属性的实际内容。
add_executable(detection examples/test_api.cpp ${INCLUDES})
生成可执行程序,这里是利用examples/test_api.cpp
来测试封装的dll是否生效。程序主要就是引用了DetectionApi.h,调用给出的函数,观察是否有打印信息。可执行程序仅用作测试dll,没有实际意义,CMake文件可以不包含这段代码。
TARGET_LINK_LIBRARIES(detection ${PROJECT_BINARY_DIR}/lib/libDetectionApi.dll.a)
为了测试dll,就不包含cpp,而是使用静态链接库替代cpp进行编译(原来exe的生成方式是.h + .cpp,现在是.h + .dll.a)。这样,可执行程序在运行时就会去同级目录下找你的dll文件了。而dll.a在此之后也就没有用了。
需要注意的是,本文操作全过程不需要修改原始代码,CMake过程是独立于代码之外的。
CMake语法内容非常庞大,在实际使用中,要多查阅CMake官方文档。
CMake Documentation
为了便于测试和发布,最好能在编译时为我们的dll库添加上版本信息。
我们需要首先在CMakeLists.txt的同级目录下建立version.rc.in
文件,里面存放Cmake时用到的一些变量。
这里给出了一个模板,具体根据需求修改。
// version.rc.in
#define VER_FILEVERSION @MY_PRODUCT_NUMBER@,@MY_PRODUCT_VERSION@,@MY_BUILD_NUMBER@,0
#define VER_FILEVERSION_STR "@MY_PRODUCT_NUMBER@.@MY_PRODUCT_VERSION@.@[email protected]\0"
#define VER_PRODUCTVERSION @MY_PRODUCT_NUMBER@,@MY_PRODUCT_VERSION@,@MY_BUILD_NUMBER@,0
#define VER_PRODUCTVERSION_STR "@MY_PRODUCT_NUMBER@.@MY_PRODUCT_VERSION@.@MY_BUILD_NUMBER@\0"
//
1 VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
/* For some reason the ProductVersion would not appear unless I add the following section: VarFileInfo */
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
然后补充CmakeLists.txt文件里的内容。
# 设置版本信息
set(MY_PRODUCT_NUMBER 1)
set(MY_PRODUCT_VERSION 0)
string(TIMESTAMP COMPILE_TIME %m%d%H%M)
set(build_time ${COMPILE_TIME})
set(MY_BUILD_NUMBER ${build_time})
...
ADD_LIBRARY(detectionapi SHARED ${INCLUDES} ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
为了图方便,这里在版本号的第三位,使用编译时当前的时间信息自动生成。
string(TIMESTAMP COMPILE_TIME %m%d%H%M)
set(build_time ${COMPILE_TIME})
set(MY_BUILD_NUMBER ${build_time})
可以在build/目录下通过cmake ..
命令,或者通过CMake-GUI编译到build/目录下。
然后在build/下通过make
命令执行生成的Makefile脚本,生成可执行程序和.dll文件。
最后通过make install
在指定位置/lib生成.dll文件。
完成后新的目录结构为:
│ CMakeLists.txt
│
├─build
│ ├─bin
│ │ detection.exe
│ ├─CMakeFiles
│ ├─lib
│ │ detectionapi.dll
│ │ libdetectionapi.dll.a
│ └─Makefile
│
├─examples
│ test_api.cpp
│
├─include
│ └─detectionapi
│ DetectionApi.h
│
├─lib
│ detectionapi.dll
│ libdetectionapi.dll.a
│ libopencv_calib3d450.dll
│ libopencv_core450.dll
│
└─src
DetectionApi.cpp
生成的可执行程序位于build/bin下,将所有需要的第三方dll库放在同级目录下,并将DetectionApi.dll
也放于此。双击.exe,成功运行。
完成dll封装,给其他开发人员调用时,就只需要提供.h和.dll了,非常的方便。
如果生成了版本信息的话,可以查看一下dll库的属性——详细信息。如果生成正确的话,文件版本和产品版本都可以看到了。