4. 代码结构设计

大家好,我是程序喵。


上一篇文章我们已经梳理了整体的流程,这篇文章正式进行代码开发。


写代码之前,首先需要设计代码的整体结构,简单粗略来说就是建立不同的目录,不同目录放置不同模块的代码。

如图:



这里我建了几个目录,主要有:

  1. include:一般SDK的产物会是个静态库/动态库 + 对外头文件,SDK的对外头文件就会放在这个include目录。
  2. src:整个SDK的核心代码,甚至可以说几乎所有代码都放在此目录。
  3. examples:这个目录会放相关的示例代码,主要给用户查看SDK相关代码的使用示例。
  4. tests:这个目录会放置相关的测试代码,主要是放置单元测试的代码,项目中开发的大多数函数的功能,建议都加在单元测试中。
  5. scripts:脚本都放在此目录,这里主要放置相关的构建脚本。


先从一个简单的函数 BuriedTest() 来搭建整个框架。



先对外暴露一个 BuriedTest() 函数,函数的声明放置在include/buried.h中:


#pragma once

int BuriedTest();


函数的定义放置在src/buried.cc中:


#include <iostream>

int BuriedTest() {
std::cout << "Test";
return 1;
}


有了头文件 + 源文件,也有了函数的声明 + 定义,如何编译成动态库、静态库呢?


这里我们使用cmake构建,cmake构建需要搭配CMakeLists.txt,这个CMakeLists.txt也可以理解为是cmake的源文件。


cmake会通过CMakeLists.txt生成整体的makefile文件,然后再通过makefile文件编译整个项目。


在编写CMakeLists.txt之前,我想有必要先介绍下在Linux下如何使用命令行编译程序,这有助于我们理解CMakeLists.txt的语法。


一个基本的命令行编译示例:


clang++ a.cc -I b -L c -l d -D EEE=HELLO -o f.elf


它表示:

  1. clang++ a.cc 表示使用clang++编译源程序
  2. -I b 表示指定b目录为头文件的寻找目录
  3. -L c 表示指定c目录为链接库的寻找目录
  4. -l d 表示程序会链接libd.a
  5. -D EEE=HELLO 表示为整个程序定义一个宏EEE,为HELLO
  6. -o f.elf 表示生成的可执行文件名为f.elf


CMakeLists.txt中对应的语句是什么呢?


  1. 编译源程序,可以使用add_executable 或 add_library,一个表示编译可执行文件,一个表示编译库文件
  2. 指定头文件的寻找目录,可以使用 include_directories
  3. 指定链接库的寻找目录,可以使用 link_directories
  4. 链接某个三方库,可以使用 target_link_libraries
  5. 定义宏,可以使用 add_definitions


介绍完核心的几个语句后,我这里贴下项目中整体的CMakeLists.txt,详见注释


# 表示要求的最小cmake版本号
cmake_minimum_required(VERSION 3.20)

# 相当于定义了一个变量 PROJECT_NAME = "BuriedPoint"
set(PROJECT_NAME "BuriedPoint")

# cmake项目根目录的CMakeLists.txt都需要有这个
project(${PROJECT_NAME} VERSION 1.1.1.1)

# 定义开关,表示是否编译动态库
option(BUILD_BURIED_SHARED_LIBS "build shared library" ON)

# 定义开关,表示是否编译静态库
option(BUILD_BURIED_STATIC_LIBS "build static library" ON)

# 定义开关,表示是否编译示例代码
option(BUILD_BURIED_EXAMPLES "build examples" OFF)

# 定义开关,表示是否编译单元测试程序
option(BUILD_BURIED_TEST "build unittest" OFF)

# MSVC 编译专用的配置,表示启动多线程编译,如果是Linux下,这个需要注释掉
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")

# 相当于 libs = libs + winmm + iphlpapi + ws2_32 + dbghelp
set(LIBS ${LIBS} winmm iphlpapi ws2_32 dbghelp)

# generate pdb file
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")

set(INCDIRS . lib ${CMAKE_BINARY_DIR})
include_directories(${INCDIRS})

# 表示启用C++20标准和C11标准
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++20")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /std:c11")

# 打印信息
message(STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
message(STATUS "CMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}")

# Debug模式下定义DEBUG宏,Release模式下定义NDEBUG宏
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
add_definitions(-DDEBUG)
else()
add_definitions(-DNDEBUG)
endif()

# 添加src子目录到项目中,子目录中也会对应有一个CMakeLists.txt
add_subdirectory(src)

if(BUILD_BURIED_EXAMPLES)
add_subdirectory(examples)
endif()

if(BUILD_BURIED_TEST)
include_directories(
googletest/googletest
googletest/googletest/include)
add_subdirectory(googletest)
add_subdirectory(tests)
endif()


再看下src中的CMakeLists.txt


# 表示项目中的头文件只会被包含一次
include_guard(GLOBAL)

set(BURIED_SRCS
buried.cc)

set(MODULE_NAME "Buried")

message(STATUS "libs ${LIBS}")

# 编译动态库
if(BUILD_BURIED_SHARED_LIBS)
add_library(${MODULE_NAME} SHARED ${BURIED_SRCS})
target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
endif()

# 编译静态库
if(BUILD_BURIED_STATIC_LIBS)
add_library(${MODULE_NAME}_static STATIC ${BURIED_SRCS})
target_link_libraries(${MODULE_NAME} PRIVATE ${LIBS})
endif()


使用CMakeLists.txt定义好了构建规则后,就需要写编译脚本了,详见注释:


scripts/build.py

# coding=utf-8
import shutil
import os
import sys
import argparse

SCRIPT_PATH = os.path.split(os.path.realpath(__file__))[0]
# 定义构建目录,build目录
BUILD_DIR_PATH = SCRIPT_PATH + '/../build'

def clear():
if os.path.exists(BUILD_DIR_PATH):
shutil.rmtree(BUILD_DIR_PATH)

def build_windows(platform='x64', config='Release', args=None):
platform_dir = '%s/%s-%s' % (BUILD_DIR_PATH, platform, config)
os.makedirs(platform_dir, exist_ok=True)

os.chdir(platform_dir)
# 生成makefile文件
build_cmd = 'cmake ../.. -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=%s -DCMAKE_GENERATOR_PLATFORM=%s -T v143' % (
config, platform)
# 如果--test,则打开BUILD_BURIED_TEST开关
if args.test:
build_cmd += ' -DBUILD_BURIED_TEST=ON'

# 如果--example,则打开BUILD_BURIED_EXAMPLES开关
if args.example:
build_cmd += ' -DBUILD_BURIED_EXAMPLES=ON'

print("build cmd:" + build_cmd)
ret = os.system(build_cmd)
if ret != 0:
print('!!!!!!!!!!!!!!!!!!build fail')
return False

# 构建
build_cmd = 'cmake --build . --config %s --parallel 8' % (config)
ret = os.system(build_cmd)
if ret != 0:
print('build fail!!!!!!!!!!!!!!!!!!!!')
return False
return True

def main():
clear()
os.makedirs(BUILD_DIR_PATH, exist_ok=True)
parser = argparse.ArgumentParser(description='build windows')
parser.add_argument('--test', action='store_true', default=False,
help='run unittest')
parser.add_argument('--example', action='store_true', default=False,
help='run examples')
args = parser.parse_args()

if not build_windows(platform='x64', config='Debug', args=args):
exit(1)

if __name__ == '__main__':
main()


整个脚本核心其实就两段:

  1. 生成makefile文件
  2. 构建


生成makefile文件的语句:


build_cmd = 'cmake ../.. -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=%s -DCMAKE_GENERATOR_PLATFORM=%s -T v143' % (config, platform)


构建语句:


build_cmd = 'cmake --build . --config %s --parallel 8' % (config)


脚本写好之后,我们就可以编译程序了,比如


python scripts\\[build.py](<http://build.py/>) --test --example


编译后我们尝试运行一下,结果如图:


从图中可以看到,咱的整个结构设计的流程已经正常跑通了。


本节具体代码详见:https://github.com/chengxumiaodaren/BuriedPoint/tree/0.1.1


下一节我会具体介绍项目中依赖的三方库,以及如何依赖三方库。下周见!


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
程序喵
下载 APP