WHCSRL 技术网

VSCode编译调试复杂C/C++项目

一、前言

今天用VS2019写项目的时候,突然给我疯狂报错,说运行时"DLL Initialization failed",我这项目完全没有任何动态库依赖,那意思你Windows自己的kernel都加载不好呗。

一气之下我决定换vscode写了,反正前端部分也是用vscode,后端能统一那是最好,查了半天,配置了一通终于搞好了,这里记录一下,也方便大家少走弯路。

二、安装编译器

VSCode本身只是一个编辑器,它是没有能力进行编译和调试的,所以需要额外装一个编译器。这里我推荐使用TDM_GCC,原因无他,一个安装包就全搞定了(记得让它自己添加环境变量),比MinGW方便点。Linux下你直接apt装一个g++就可以了。

这里安装结束后还需要做一个小修改,到TDM-GCC-64in中找到mingw32-make.exe,把它重命名成make.exe,这样是为了和Linux下指令统一。

三、创建项目目录

创建项目时,结构清晰总是没有错的。按照我个人的习惯,会把项目目录建成下面的格式:

├── include
├── src
├── lib
├── test
└── output
  • 1
  • 2
  • 3
  • 4
  • 5
  1. include,用于存放所有头文件
  2. src,用于存放所有源文件
  3. lib,用于存放可能的动态链接库
  4. test,用于存放测试脚本
  5. output,用于存放编译后的可执行文件

四、Makefile

“Building Tool is The Key”,Makefile就是我选择的构建工具。虽然一般复杂一些的项目都是用CMake,但是作为Linux自带,GCC附带的make工具足够我们平时的项目使用了。

下面我给出一份Linux和Windows(需要安装TDM_GCC)通用的Makefile文件:

#
# 'make'        build executable file 'main'
# 'make clean'  removes all .o files
# 'make clean_all' removes all .o and executable files

# define debug/release mode
ver = release

# define Platform Architecture(32/64)
ARCH := 64

# define the Cpp compiler to use
CXX := g++

# define any compile-time flags
ifeq ($(ver), release)
CXXFLAGS	:= -std=c++17 -O3
else
CXXFLAGS	:= -std=c++17 -Wall -g
endif

# define library paths in addition to /usr/lib
#   if I wanted to include libraries not in /usr/lib I'd specify
#   their path using -Lpath, something like:
LFLAGS =

# define program name
PROGRAM	:= cploxplox

# define output directory
OUTPUT	:= output

# define source directory
SRC		:= src

# define include directory
INCLUDE	:= include

# define lib directory
LIB		:= lib/lib$(ARCH)

ifeq ($(OS),Windows_NT)
MAIN	:= $(PROGRAM).exe
SOURCEDIRS	:= $(SRC) $(SRC)/*
INCLUDEDIRS	:= $(INCLUDE)
LIBDIRS		:= $(LIB)/win
FIXPATH = $(subst /,,$1)
RM			:= del /q /f
MD	:= mkdir
else
MAIN	:= $(PROGRAM)
SOURCEDIRS	:= $(shell find $(SRC) -type d)
INCLUDEDIRS	:= $(shell find $(INCLUDE) -type d)
LIBDIRS		:= $(shell find $(LIB)/linux -type d)
FIXPATH = $1
RM = rm -f
MD	:= mkdir -p
endif

# define any directories containing header files other than /usr/include
INCLUDES	:= $(patsubst %%,-I%%, $(INCLUDEDIRS:%%/=%%))

# define the C libs
LIBS		:= $(patsubst %%,-L%%, $(LIBDIRS:%%/=%%)) $(patsubst $(LIBDIRS)/lib%%.a,-l%%, $(wildcard $(LIBDIRS)/*.a)) 

# define the C source files
SOURCES		:= $(wildcard $(patsubst %%,%%/*.cpp, $(SOURCEDIRS)))

# define the C object files 
OBJECTS		:= $(SOURCES:.cpp=.o)

#
# The following part of the makefile is generic; it can be used to 
# build any executable just by changing the definitions above and by
# deleting dependencies appended to the file from 'make depend'
#

OUTPUTMAIN	:= $(call FIXPATH,$(OUTPUT)/$(MAIN))

all: $(OUTPUT) $(MAIN)
	@echo Executing 'all' complete!

$(OUTPUT):
	$(MD) $(OUTPUT)

$(MAIN): $(OBJECTS) 
	$(CXX) $(CXXFLAGS) $(INCLUDES) -o $(OUTPUTMAIN) $(OBJECTS) $(LFLAGS) $(LIBS)

# this is a suffix replacement rule for building .o's from .c's
# it uses automatic variables $<: the name of the prerequisite of
# the rule(a .c file) and $@: the name of the target of the rule (a .o file) 
# (see the gnu make manual section about automatic variables)
.cpp.o:
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c $<  -o $@

.PHONY: clean clean_all
clean:
	$(RM) $(call FIXPATH,$(OBJECTS))
	@echo Cleanup .o files complete!

clean_all:
	$(RM) $(OUTPUTMAIN)
	$(RM) $(call FIXPATH,$(OBJECTS))
	@echo Cleanup all complete!

run: all
	./$(OUTPUTMAIN)
	@echo Executing 'run: all' complete!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

这份Makefile文件是完全对应之前的项目结构,支持src和Include中继续设置子目录。

  1. make (ver=release/debug)。构建项目,默认以release模式构建
  2. make clean。删除所有生成的.o文件
  3. make clean_all。删除所有生成的文件
  4. make run。编译并运行生成的可执行文件

五、VSCode设置

请确认安装了C/C++插件

5.1 tasks.json

VSCode虽然不能编译代码,但是它可以执行脚本,也就是自带的Task Runner,我们就是基于此来实现编译调试。

首先ctrl+shift+p调出命令栏,输入task,应该可以看到任务:配置任务,回车之后会让你选择模板(取决于你当前打开的文件),我们不需要,随便选生成tasks.json文件即可。打开替换成以下内容:

{
    "tasks": [
        {
            "type": "shell",
            "label": "build",
            "command": "make ver=debug -j4",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": "$gcc"
        },
    ],
    "version": "2.0.0"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这个文件是用来自定义任务的,我们这里定义了:

  1. label:一个叫做“build”的任务
  2. type:这是一个用命令行(shell)执行的任务
  3. command:它执行的指令是"make ver=debug -j4"。也就是以debug模式编译,-j4是启用四个线程,可以加快编译速度
  4. group:这个是用来配合快捷键的,当kind设置为"build"后,你可以用ctrl+shift+b快速构建
  5. problemMatcher:当你的命令执行并报错后,将交由gcc负责处理并转发到VSCode的Problems界面。

5.2 launch.json

任务定义好之后需要有人来执行,VSCode左侧菜单栏的第四项Debug点击后,可以创建launch.json文件。同样的,随便选模板,替换为以下内容:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/output/main.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "E:\Develop\TDM-GCC-64\bin\gdb.exe",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "build"
        }
    ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里我们定义了按下F5之后运行的流程:

  1. name:启动的是脚本名叫"(gdb) Launch"
  2. type:cppdbg,cpp debug
  3. program:启动的文件,这里你需要给出你自己设定的可执行文件名字(我这儿给的main.cpp)。
  4. MIMode:gdb。如果你需要Debug,那么这里必须设置,支持gdb和lldb。
  5. miDebuggerPath:请修改为你自己的gdb所在路径
  6. preLaunchTask:意思是在启动program之前,先要做什么,那肯定需要先编译啊,所以我们给了上面tasks中设置的任务名,“build”。

六、Go!

好了,此时你只需要按下F5,或者在顶部菜单中选:运行->启动调试,就大功告成了。

Debug实例

七、注意

  1. 如果用VSCode作为C/C++的编程环境,一些编辑器相关的问题,比如额外的IncludePath,需要在ctrl+shift+p,寻找C/C++: 编辑配置(JSON),然后修改。
  2. GDB在Windows下不支持动态断点,也就是说你需要先打好断点,再F5启动,否则是断不住的。

八、参考

  1. Setup Visual Studio Code for Multi-File C++ Projects
  2. Debug a C++ project in VS Code
推荐阅读