日前在学习制作LearnOpenGL教程的实战项目Breakout游戏时,希望能将这个小游戏开发成跨平台的,支持在多个平台运行。工欲善其事必先利其器,首先需要做的自然是搭建一个舒服的跨平台C/C++开发环境,所以这篇文章主要就是记录环境搭建的整个过程,踩到的一些坑,以及对应的解决办法。

正文开始之前,先来阐述几个问题

  • 为什么选择使用VSCode
  1. 实在用不习惯Visual Studio(也可能是用的太少了T▽T)
  2. 代码编辑方面更喜欢用轻量级的编辑器,比如Sublime或者VSCode
  3. VSCode确实比较强大好用,插件丰富
  • 为什么使用CMake
  1. 通用的编译构建工具,跨平台的关键,一份代码,CMake可以针对不同的系统编译构建生成不同的项目工程
  2. 源代码管理,编译更加方便(如果仅仅使用VSCode搭建开发环境,则每添加一个源文件,就要改动一下编译指令)
  • 最终实现的开发流程是怎样的
  1. VSCode编写代码
  2. 快捷键Ctrl+Shift+B,调用CMake完成本地项目生成(Mac快捷键Command+Shift+B
  3. 快捷键Ctrl+B,完成项目的编译构建与运行(Mac快捷键Command+B
  4. 快捷键F5,完成项目的调试与运行(VSCode的F5调试运行,为了能够实现调试功能额外做了许多工作,所以启动会有些慢,因此在不需要调试的时候,直接使用Ctrl+B编译运行看效果会更快些)

开发工具

CMake与VSCode

  • CMake的获取,可以查看CMake官网
  • VSCode的获取,可以查看VSCode官网
  • 关于CMake与VSCode如何安装,比较简单,网上也有很多教程,这里就不详细介绍了

VSCode插件推荐安装

  • 在VSCode的Extensions面板中搜索下面的插件名即可,记得看清作者名,不要下错啦
插件名 作者 描述
C/C++ Microsoft 提供C/C++的代码提示,跳转,调试等诸多功能,官方出品,基本是C/C++开发必备了
CMake twxs 提供CMake语法的高亮显示以及代码段提示

示例项目

这里给出例子工程的文件目录情况,并不完整,但具有一定的代表性,不仅涉及源代码的编译,同样包含了静态库,动态库的加载,以及资源文件的读取等问题

Breakout
├── 3rd // 第三方库
│ ├── glfw // 一个静态库目录
│ ├── irrKlang-1.6.0 // 一个动态库目录
│ └── FindIrrKlang.cmake // cmake文件
├── resources // 资源目录
│ ├── textures // 存放贴图文件
│ └── shaders // 存放shader文件
├── src // 源代码目录
│ ├── game // 源代码子目录
│ │ ├── game.h
│ │ └── game.cc
│ └── main.cc
└── CMakeLists.txt // cmake文件

使用CMake

本文仅着重介绍为了完成示例项目开发,解决特定问题而使用的一些cmake语句,详细的cmake介绍可以查看这份还不错的文档或者这篇博文

cmake的所有语句都写在CMakeLists.txt中,cmake会根据该文件中的配置完成最终的编译,构建,打包,测试等一系列任务

一个简单的CMakeLists.txt如下所示,完整的文件可以查看这里

# cmake最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 设置PROJECT_NAME变量
set(PROJECT_NAME Breakout)
# 设置工程名
project (${PROJECT_NAME})
# 查找当前目录下的所有源文件并存入DIR_SRCS变量
aux_source_directory(src DIR_SRCS)
# 添加一个可编译的目标到工程
add_executable (${PROJECT_NAME} ${DIR_SRCS})

如何编译一个文件夹下的所有源代码

在开发过程中,由于架构设计或是为了便于管理与查找,源文件一般会根据不同的功能存放在不同的文件夹中,文件夹中又可能嵌入文件夹,所以需要一条语句能够获取所有的源文件进行编译,而不用每新创建一个源文件,就修改一次编译指令

# 递归列出所有源文件
file (GLOB_RECURSE SOURCE_FILES *.cc)
# 添加一个可编译的目标到工程
add_executable (${PROJECT_NAME} ${SOURCE_FILES})

上面这条file命令会递归列出所有.cc文件,并存入SOURCE_FILES变量,然后将SOURCE_FILES表示的所有.cc文件添加到目标即可,从而解决多源文件编译问题

如何引入一个第三方静态库

为了不重复造轮子,开发中不可避免的要引入其他第三方库。正常情况下,这个第三方库也会是一个CMake工程(或是库的开发者直接提供已经编译好的库)

以示例项目引入的glfw库为例

  1. 添加submodule引入glfw(glfw正好是github上的一个开源项目),或是直接将第三方库的源码放到自己的目录中
git submodule add https://github.com/glfw/glfw.git
  1. 使用add_subdirectory命令,将glfw所在的文件夹添加到编译的任务列表中
# 保证glfw dir被编译
add_subdirectory (${GLFW_DIR})
  1. 将glfw的头文件目录添加到头文件搜索路径中
# 添加头文件搜索路径
include_directories (${GLFW_DIR}/include)
  1. 链接glfw库,target_link_libraries命令用来链接目标与库文件,第一个参数就是我们的构建目标,后面可以跟多个参数,来表示链接多个库
# 添加链接库
target_link_libraries (${PROJECT_NAME} glfw)

动态库的加载问题

示例项目引入的irrKlang库为例,它并不是一个开源项目,不过好在它提供了已经在多个平台上编译好的库,所以我们需要根据不同的平台来设置引入不同的库文件

  1. 利用find_package引入外部依赖包,它可以帮我们找到官方预定义的许多依赖包模块,当未在官方预定义的依赖中找到时,会再查找FindXXX.cmake文件,执行该文件从而找到XXX库。更详细的介绍可以查看这篇文章
# IrrKlang
find_package (IrrKlang REQUIRED)
  1. 先新建FindIrrKlang.cmake文件,由它来负责具体的irrKlang库加载。部分语句如下所示,主要是根据当前平台的不同,设置不同的头文件路径,库路径,库所在目录等变量。用到的find_library语句可以实现直接根据库的base name(即不需要lib,so等),找到对应的库,并存入IRRKLANG_LIBRARY变量
find_path(IRRKLANG_INCLUDE_DIR NAMES irrKlang.h PATHS "${3RD_DIR}/irrKlang-1.6.0/include")
IF(WIN32) # win32平台
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Using MSVC")
set (IRRKLANG_LIB_DIR "${3RD_DIR}/irrKlang-1.6.0/lib/Win32-visualStudio")
set (IRRKLANG_BIN_DIR "${3RD_DIR}/irrKlang-1.6.0/bin/Win32-visualStudio")
find_library(IRRKLANG_LIBRARY NAMES irrKlang PATHS ${IRRKLANG_LIB_DIR})
elseif("${CMAKE_CXX_COMPILED_ID}" STREQUAL "GNU")
message(STATUS "Using GCC")
set (IRRKLANG_LIB_DIR "${3RD_DIR}/irrKlang-1.6.0/lib/Win32-gcc")
set (IRRKLANG_BIN_DIR "${3RD_DIR}/irrKlang-1.6.0/bin/Win32-gcc")
find_library(IRRKLANG_LIBRARY NAMES libirrKlang.a PATHS ${IRRKLANG_LIB_DIR})
endif() elseif(APPLE) # mac平台
set (IRRKLANG_BIN_DIR "${3RD_DIR}/irrKlang-1.6.0/bin/macosx-gcc")
find_library(IRRKLANG_LIBRARY NAMES libirrklang.dylib PATHS "${3RD_DIR}/irrKlang-1.6.0/bin/macosx-gcc") elseif(UNIX AND NOT APPLE) # 等同于linux平台
set (IRRKLANG_BIN_DIR "${3RD_DIR}/irrKlang-1.6.0/bin/inux-gcc")
find_library(IRRKLANG_LIBRARY NAMES IrrKlang PATHS "${3RD_DIR}/irrKlang-1.6.0/bin/linux-gcc") endif()
  1. 将找到的irrKlan头文件添加到头文件搜索路径中
include_directories (${IRRKLANG_INCLUDE_DIR})
  1. 链接irrKlang库
# 添加链接库
target_link_libraries (${PROJECT_NAME} glfw ${IRRKLANG_LIBRARY})

utf-8编码格式的代码通过visual studio编译报错问题

跨平台的代码,一般使用utf-8编码格式的代码,更加通用,也可以保证在MacOS或者Linux平台上的正常编译。

但是visual studio默认编译文件的编码是utf-8 with bom,在没有中文的情况下,直接编译是没有问题的,然而当源文件含有中文时(比如中文注释),则可能会出现异常,报一些莫名其妙的语法错误。解决办法是通过CMake语句通知MSVC编译时采用utf-8编码

# 设置MSVC编译编码
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>")

可执行文件的工作目录问题

当编译构建生成可执行文件后,我们希望可以直接通过命令行命令启动可执行文件来查看效果。但是由于工作目录的问题,可能会导致出现资源文件找不到,或者库加载失败问题

对于visual studio工程,可以通过CMake语句设置其工作目录,但是这个工作目录仅在通过visual studio调试启动时才会生效,但对于直接启动可执行文件的情况仍然是没用的

# 设置工作目录
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY
${CMAKE_SOURCE_DIR}/resources
)

所以只能将资源文件目录放置到可执行文件所在目录下,以保证一定能加载到需要的资源,这可以通过CMake的自定义命令实现

# 复制资源文件到工作目录
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/resources
$<TARGET_FILE_DIR:${PROJECT_NAME}>/resources
)

这条语句的功能是在编译结束后,将指定的资源目录复制到生成的可执行文件所在的目录。

同样的,对于一些动态库,比如dylib,dll等也需要复制,不过注意最好在编译前就将它们复制到目标目录,使用PRE_BUILD指明命令执行的时机

# 复制库到工作目录
add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${IRRKLANG_BIN_DIR}
$<TARGET_FILE_DIR:${PROJECT_NAME}>
)

关于add_custom_command详细介绍可以查看这里

如何修改Mac上动态库的加载路径

在Mac上启动可执行文件时,一直遇到一个动态库无法加载的报错

dyld: Library not loaded: /usr/local/lib/libirrklang.dylib
Referenced from: ...
Reason: image not found

这是由于在编译生成动态库时,可以指定动态库的加载路径,比如我们引入的libirrklang库默认会到/usr/local/lib目录下查找dylib文件

简单的解决方式,自然是通过install命令,将libirrklang.dylib文件安装到/usr/local/lib目录下,不过为了不“污染”其他目录,更希望可执行文件加载的是我们放置在其所在目录下的libirrklang.dylib文件。为了实现这个目标,我们添加下面的CMake语句

if(APPLE)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND install_name_tool -change /usr/local/lib/libirrklang.dylib @executable_path/libirrklang.dylib ${PROJECT_NAME}
)
endif()

判断如果是Mac平台,则通过add_custom_command调用install_name_tool命令,来修改应该应用程序对动态库的查找路径。其中的@executable_path就表示可执行文件所在目录

关于动态库加载路径涉及到的rpath,executable_path,install_name_tool的详细介绍可以查看这里

利用VSCode的task.json执行指定命令

CMake的配置文件已经基本准备完毕了,接下来就是怎样结合VSCode更方便的调用CMake的问题了

通过Ctrl+Shift+B执行CMake编译本地工程

Ctrl+Shift+B是VSCode调用task的默认快捷键,task定义在.vscode目录下的task.json文件中,一般使用task来完成编译等任务,VSCode任务系统提供了丰富的参数配置,我们可以利用它完成很多自定义任务

  • 定义一个task,执行cmake ..命令,完成本地项目的编译生成。Linux会生成Makefile,MacOS生成Makefile或Xcode工程,Windows下生成Visual Studio工程。注意这里的..表示的是上层目录,因为我们会在项目根目录下新建一个build文件夹,然后在这个文件夹内完成一系列的编译工作,这样cmake生成的中间文件都在build目录,不会“污染”开发目录(将build目录加入.gitignorej即可忽略CMake所产生的所有中间文件),在编译出问题的时候也可以直接删除buidl目录重新编译
{
"label": "cmake", // task的名字
"type": "shell",
"command": "cmake",
"args": [
// "-DCMAKE_BUILD_TYPE=${input:CMAKE_BUILD_TYPE}",
".."
],
"options": {
"cwd": "build" // 表示当前执行目录build文件夹
},
"group": "build",
"presentation": { // 一些控制台显示配置
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile",
"dependsOn":["mkbuild"] // 依赖的任务,在本任务执行前先执行mkbuild任务
}
  • cmake ..命令执行前,先通过task执行mkdir新建build文件夹。其中通过windows参数,区分不同平台设置不同的参数
{
"label": "mkbuild",
"type": "shell",
"command": "mkdir", // 调用的命令
"args": [ // 命令参数
"-p",
"build"
],
"windows":{ // windows平台使用mkdir -Force build新建文件夹
"args": [
"-Force",
"build"
]
},
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile",
}
  • 当按下Ctrl+Shift+B时,VSCode将弹出所有可执行的task,选择执行cmake task即可。由于定义了依赖(dependsOn),在cmak task执行前,将自动先执行mkbuild task

通过Ctrl+B构建与运行可执行文件

  • 在启动可执行文件前,先通过cmake --build .构建生成可执行文件
{
"label": "compile",
"type": "shell",
"command": "cmake --build .",
"options": {
"cwd": "build"
},
"group": "build",
"presentation": {
// Reveal the output only if unrecognized errors occur.
"reveal": "always",
"clear": true
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile"
}
  • 在可执行文件生成后,通过task启动可执行文件。其中的${workspaceFolderBasename}是VSCode的内置变量,表示工程名。更多的内置变量介绍可以查看这里
{
"label": "run",
"type": "shell",
"command": "./${workspaceFolderBasename}",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": true
},
"options": {
"cwd": "build"
},
"windows":{
"options": {
"cwd": "build/Debug" // windows visual studio项目会默认多生成Debug/Release目录
},
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
"problemMatcher": "$msCompile",
"dependsOn":["compile"] // 在run任务执行前先执行compile任务,确保修改的代码生效
}
  • 通过Ctrl+B直接调用run任务。由于编译运行这个任务经常要用到,如果依然通过Ctrl+Shift+B弹出所有任务,再进一步选择就会有些麻烦,所以定义一个快捷键(读者可以用同样的方法设置自己喜欢的快捷键)来直接运行run任务
  1. 打开VSCode快捷键设置

  1. 在弹出的界面中输入“run build task”搜索,并修改其快捷键

  1. 点击右上角的翻转按钮,进入快捷键文件配置

  1. 在打开的文件中,为刚配置的快捷键添加参数,告诉它直接启动名字叫“run”的task
{
"key": "ctrl+b",
"command": "workbench.action.tasks.runTask",
"args" : "run"
}

利用VSCode的launch.json完成调试

VSCode通过.vscode目录下的launch.json实现对多个平台多种语言的调试支持

通过F5完成项目的调试与运行

  • 配置launch.json文件以支持C/C++项目的调试
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Debug", //名称
"type": "cppdbg", //调试类型,除使用msvc进行调试外,均为该类型
"request": "launch",
"program": "${workspaceFolder}/build/${workspaceFolderBasename}", //指定C/C++程序位置
"args": [], //指定运行参数
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build", //指定工作目录
"preLaunchTask": "compile", //在调试前会先调用这个task编译构建程序
"environment": [],
"externalConsole": false,
"osx": { //macOS的特定配置
// "miDebuggerPath": "/Applications/Xcode.app/Contents/Developer/usr/bin/lldb-mi", //修改使用的lldb-mi,一般不需要修改
"MIMode": "lldb" //指定使用lldb进行调试
},
"linux": { //linux的特定配置
"MIMode": "gdb", //指定使用gdb调试
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
},
"windows": { //windows的特定配置
"type": "cppvsdbg", //指定使用msvc进行调试
"program": "${workspaceFolder}/build/Debug/${workspaceFolderBasename}.exe", //指定C/C++程序位置
}
}
]
}
  • F5是VSCode调试运行的默认快捷键,不需要额外配置,启动调试后的界面如下所示

通过使用本地工具完成调试

  • 由于cmake在windows平台会默认生成Visual Stduio工程,所以也可以直接打开生成的解决方案,通过Visual Studio进行调试
  • Mac平台可以使用cmake .. -GXcode命令指定生成XCode工程,然后通过XCode进行调试

参考链接

使用VSCode和CMake构建跨平台的C/C++开发环境的更多相关文章

  1. 跨平台C/C++集成开发环境-Code::Blocks-内置GCC

    Code::Blocks 是一个开放源码的全功能的跨平台C/C++集成开发环境. 相比于基于Delphi的Dev-C++共享C++IDE,Code::Blocks是开放源码软件.Code::Block ...

  2. 一.1搭建跨平台的统一python开发环境

    搭建跨平台的统一python开发环境: 使用开发环境的好处: 可不用在服务器上直接修改源代码---写的代码首先得入版本库(放git或giitlab中),在本地写代码提交到git中.然后在服务器上git ...

  3. 在win10系统环境下,安装配置sublime 3,构建python和vue.js开发环境(插件)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_131 疫情当下,最近一直在用mac下的虚拟机运行win10系统,由于在线人数过多,直播授课的时候使用vscode的时候内存暴涨,于 ...

  4. 使用Vscode和Cmake打造跨平台的C++ IDE

    准备工作 Viusal Studio Code 64位 :Download Visual Studio Code - Mac, Linux, Windows Cmake 3.4 :Download | ...

  5. VsCode从零开始配置一个属于自己的Vue开发环境

    vscode vue VsCode算是比较热门的一个代码编辑器了,全名Visual Studio Code下载地址:点我去下载插件众多,功能齐全,我在平常开发过程中都是用的它,整理了些自认好用的插件, ...

  6. 快速打造跨平台开发环境 vagrant + virtualbox + box

    工欲善其事必先利其器,开发环境 和 开发工具 就是 我们开发人员的剑,所以我们需要一个快并且好用的剑 刚开始做开发的时候的都是把开发环境 配置在 自己的电脑上,随着后面我们接触的东西越来越多,慢慢的电 ...

  7. VSCode, Django, and Anaconda开发环境集成配置[Windows]

    之前一直是在Ubuntu下进行Python和Django开发,最近换了电脑,把在Virtual Box 下跑的Ubuntu开发机挪过来总是频繁崩溃,索性就尝试把开发环境挪到Windows主力机了. 不 ...

  8. 如何开发Vite3插件构建Electron开发环境

    新用户购买<Electron + Vue 3 桌面应用开发>,加小册专属微信群,参与群抽奖,送<深入浅出Electron>.<Electron实战>作者签名版. 1 ...

  9. 跨平台移动框架iMAG开发入门

    iMAG是一个非常简洁高效的移动跨平台开发框架,开发一次能够同一时候兼容Android和iOS平台,有点儿Web开发基础就能非常快上手.当前移动端跨平台开发的框架有非常多,但用iMAG另一个优点,就是 ...

随机推荐

  1. DataGrid添加进度条列

    DataGridColumn类型的继承树 DataGridColumn的派生类: 一般情况下DataGridBoundColumn和DataGridComboBoxColumn足以满足多数列的样式,如 ...

  2. 四则运算(C语言实现)

    四则运算(c语言实现) 合伙人:魏甫——3118004973  ,温钦益——3118004975 https://github.com/iamdate/work/tree/master 一.项目及其要 ...

  3. JavaFX桌面应用-SpringBoot + JavaFX

    SpringBoot对于Java程序员来说可以是一个福音,它让程序员在开发的时候,大大简化了各种spring的xml配置. 那么在JavaFX项目使用SpringBoot会是怎么样的体验呢? 这次使用 ...

  4. Dockerfile文件万字全面解析

    阅读目录 目录 阅读目录 用法 格式 Parser directives escape 环境替换 .dockerignore file FROM RUN CMD LABEL MAINTAINER EX ...

  5. 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单

    接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...

  6. 据说这个是可以撸到2089年的idea2020.2

    声明:本教程 IntelliJ IDEA IDEA2020.2破解 激活方式均收集于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除 注意: 本教程适用于 JetBrains 全系列产品 I ...

  7. 以Winsows Service方式运行JupyterLab

    有数据分析,数据挖掘,以及机器学习和深度学习实践经验的读者应该会对Jupyter Notebook这一工具十分熟悉,而JupyterLab是它的升级版本,其提供了更具扩展性,更加可定制化的功能选项. ...

  8. mysql建数据库的字符集与排序规则说明

    本文转自https://blog.csdn.net/qq_38224812/article/details/80745868,感谢作者,自己留存已备他日使用 1.字符集说明: 一般选择utf8.下面介 ...

  9. 新手oracle重启、监听

    有一次遇到了记录下. #su到oracle用户下 [root@localhost ~]# su - oracle  #重启数据库:[oracle@localhost ~]$ sqlplus /nolo ...

  10. WPF实现的加载动画

    2020-09-03 09:43:30 xaml代码 <Grid x:Name="LayoutRoot" Background="Transparent" ...