📖 本节导读
🎯 学习目标
学完本节后,你将掌握如何在 Windows 平台上搭建 Kohi 引擎的开发环境,理解项目的基本结构,并成功编译运行第一个测试程序。
⏱️ 预计时间:20 分钟 💪 难度等级:⭐☆☆☆☆(入门级) 🏷️ 关键词:
环境搭建DLL开发VS Code配置Clang编译
📑 目录导航(点击展开)
📋 前置知识
学习本节内容前,你需要了解:
|
✅ 必备知识
|
💡 加分项
|
💡 学习建议 如果你还不熟悉这些概念,建议先学习 C 语言基础和命令行操作。不过即使是初学者,跟着本教程一步步操作也能成功完成配置。
🎯 什么是项目初始配置?
📌 基本概念
项目初始配置是指为一个软件项目建立基础开发环境的过程。对于 Kohi 游戏引擎项目来说,这包括:
graph LR
A[🛠️ 开发工具配置] --> B[📦 构建系统]
B --> C[📁 项目结构]
C --> D[💻 基础代码]
style A fill:#e1f5ff
style B fill:#fff3e0
style C fill:#f3e5f5
style D fill:#e8f5e9
| 配置项 | 说明 | 作用 |
|---|---|---|
| 🛠️ 开发工具配置 | 设置代码编辑器(VS Code)的工作环境 | 提供代码补全、调试等功能 |
| 📦 构建系统 | 创建编译脚本来自动化构建流程 | 一键编译,提高开发效率 |
| 📁 项目结构 | 划分引擎代码和测试代码的目录结构 | 代码组织清晰,易于维护 |
| 💻 基础代码 | 编写最小可运行的代码来验证环境 | 确保环境配置正确 |
🤔 为什么需要它?
|
❌ 没有标准配置时
|
✅ 有了标准配置后
|
⚙️ 工作原理
构建流程可以用下面的流程图表示:
┌─────────────────────────────────────────────────────────┐
│ 📝 源代码文件 │
│ (.c / .h 文件) │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 🔨 编译器 (Clang) │
│ 将源代码编译成目标文件 (.obj) │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 🔗 链接器 (Linker) │
│ 链接成可执行文件 (.exe) 或动态库 (.dll) │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ ▶️ 运行程序 │
│ engine.dll + testbed.exe │
└─────────────────────────────────────────────────────────┘
💡 核心思想 构建脚本自动化了整个过程,VS Code 配置让编辑器理解项目结构。
🔧 Kohi 引擎的项目结构
📂 目录树
在这次初始配置中,建立了以下项目结构:
Kohi/ # 项目根目录
│
├── 📁 .vscode/ # VS Code 编辑器配置
│ ├── c_cpp_properties.json # 🔧 C/C++ 智能感知配置
│ ├── launch.json # 🐛 调试器配置
│ ├── tasks.json # ⚙️ 构建任务配置
│ ├── settings.json # 🎨 编辑器设置
│ ├── keybindings.json # ⌨️ 快捷键配置
│ └── extensions.json # 🧩 推荐扩展
│
├── 📁 engine/ # 引擎核心代码
│ ├── 📁 src/
│ │ ├── defines.h # 📋 基础类型定义和平台检测
│ │ ├── test.h # 🧪 测试接口
│ │ └── test.c # ✅ 测试实现
│ └── build.bat # 🔨 引擎编译脚本
│
├── 📁 testbed/ # 测试应用程序
│ ├── 📁 src/
│ │ └── main.c # 🚀 测试程序入口
│ └── build.bat # 🔨 测试程序编译脚本
│
├── 📁 bin/ # 编译输出目录(自动创建)
│ ├── engine.dll # 生成的引擎动态库
│ ├── engine.lib # 引擎导入库
│ └── testbed.exe # 测试程序可执行文件
│
├── build-all.bat # ⚡ 一键编译所有模块
└── .gitignore # 🚫 Git 忽略文件配置
📝 核心文件说明
🔷 defines.h - 基础类型系统
| 🎯 用途 | 定义跨平台的基础数据类型和平台检测宏 |
| ⚡ 核心功能 |
• 固定大小的整数类型 • 平台检测宏定义 • DLL 导入/导出机制 |
主要类型定义:
| 类型定义 | 说明 | 大小 | 使用场景 |
|---|---|---|---|
u8, u16, u32, u64 | 无符号整数 | 1, 2, 4, 8 字节 | 计数、索引、位操作 |
i8, i16, i32, i64 | 有符号整数 | 1, 2, 4, 8 字节 | 需要负数的场景 |
f32, f64 | 浮点数 | 4, 8 字节 | 坐标、角度、物理计算 |
b8, b32 | 布尔类型 | 1, 4 字节 | 标志位、开关状态 |
平台检测宏:
| 宏定义 | 说明 | 用途 |
|---|---|---|
KPLATFORM_WINDOWS | Windows 平台标识 | 条件编译 Windows 特定代码 |
KPLATFORM_LINUX | Linux 平台标识 | 条件编译 Linux 特定代码 |
KAPI | DLL 导入/导出宏 | 标记跨 DLL 边界的函数 |
使用场景:
✅ 场景 1:确保数据类型大小在所有平台上一致 ✅ 场景 2:编写跨平台代码时进行条件编译 ✅ 场景 3:简化类型名称,提高代码可读性
💻 实战:搭建开发环境
🎬 快速开始流程
graph TD
A[开始] --> B[安装工具]
B --> C[创建目录结构]
C --> D[编写基础代码]
D --> E[创建构建脚本]
E --> F[配置 VS Code]
F --> G[编译运行]
G --> H[成功!]
style A fill:#e1f5ff
style H fill:#c8e6c9
style B fill:#fff9c4
style C fill:#fff9c4
style D fill:#fff9c4
style E fill:#fff9c4
style F fill:#fff9c4
style G fill:#fff9c4
🛠️ 步骤 1:安装必要的工具
🎯 目标:安装 Kohi 引擎开发所需的基础工具
📦 需要安装的工具
| 工具 | 说明 | 下载地址 |
|---|---|---|
| Visual Studio Code | 🖥️ 轻量级代码编辑器 | code.visualstudio.com/ |
| Clang 编译器 | 🔧 C/C++ 编译器(LLVM) | llvm.org/ |
| Vulkan SDK | 🎮 图形 API(后续会用到) | vulkan.lunarg.com/ |
| Git | 📚 版本控制工具(可选) | git-scm.com/ |
✅ 安装后验证
# 在 PowerShell 或 CMD 中运行
clang --version # 应该显示 Clang 版本信息
预期输出示例:
clang version 15.0.0
Target: x86_64-pc-windows-msvc
Thread model: posix
💡 重要提示
安装完 Clang 后,需要将其添加到系统环境变量 PATH 中,这样才能在命令行中直接使用。
设置方法:
- 右键"此电脑" → 属性 → 高级系统设置
- 环境变量 → 系统变量 → Path → 编辑
- 新建 → 添加
C:\Program Files\LLVM\bin- 确定 → 重启命令行窗口
📁 步骤 2:创建项目结构
🎯 目标:建立符合 Kohi 引擎的目录结构
🖱️ 创建目录
# 创建项目根目录
mkdir Kohi
cd Kohi
# 创建引擎目录
mkdir engine
mkdir engine\src
# 创建测试程序目录
mkdir testbed
mkdir testbed\src
# 创建 VS Code 配置目录
mkdir .vscode
⚠️ 注意事项
bin/目录无需手动创建,编译脚本会自动创建它- 目录名称区分大小写(虽然 Windows 不区分,但为了跨平台兼容性,建议保持一致)
- 推荐使用命令行创建,确保目录结构正确
📝 步骤 3:编写基础类型定义
🎯 目标:创建引擎的基础类型系统
📄 代码:engine/src/defines.h
#pragma once
// ============================================
// 基础整数类型定义
// ============================================
// 无符号整数类型
typedef unsigned char u8; // 8位无符号整数 [0, 255]
typedef unsigned short u16; // 16位无符号整数 [0, 65535]
typedef unsigned int u32; // 32位无符号整数 [0, 4294967295]
typedef unsigned long long u64; // 64位无符号整数 [0, 18446744073709551615]
// 有符号整数类型
typedef signed char i8; // 8位有符号整数 [-128, 127]
typedef signed short i16; // 16位有符号整数 [-32768, 32767]
typedef signed int i32; // 32位有符号整数 [-2147483648, 2147483647]
typedef signed long long i64; // 64位有符号整数 [-9223372036854775808, 9223372036854775807]
// ============================================
// 浮点数类型定义
// ============================================
typedef float f32; // 32位浮点数(单精度)
typedef double f64; // 64位浮点数(双精度)
// ============================================
// 布尔类型定义
// ============================================
typedef int b32; // 32位布尔类型
typedef char b8; // 8位布尔类型
// ============================================
// 静态断言宏定义
// ============================================
#if defined(__clang__) || defined(__gcc__)
#define STATIC_ASSERT _Static_assert
#else
#define STATIC_ASSERT static_assert
#endif
// ============================================
// 编译时类型大小验证
// ============================================
STATIC_ASSERT(sizeof(u8) == 1, "Expected u8 to be 1 byte.");
STATIC_ASSERT(sizeof(u16) == 2, "Expected u16 to be 2 bytes.");
STATIC_ASSERT(sizeof(u32) == 4, "Expected u32 to be 4 bytes.");
STATIC_ASSERT(sizeof(u64) == 8, "Expected u64 to be 8 bytes.");
STATIC_ASSERT(sizeof(i8) == 1, "Expected i8 to be 1 byte.");
STATIC_ASSERT(sizeof(i16) == 2, "Expected i16 to be 2 bytes.");
STATIC_ASSERT(sizeof(i32) == 4, "Expected i32 to be 4 bytes.");
STATIC_ASSERT(sizeof(i64) == 8, "Expected i64 to be 8 bytes.");
STATIC_ASSERT(sizeof(f32) == 4, "Expected f32 to be 4 bytes.");
STATIC_ASSERT(sizeof(f64) == 8, "Expected f64 to be 8 bytes.");
// ============================================
// 布尔值常量
// ============================================
#define TRUE 1
#define FALSE 0
// ============================================
// 平台检测
// ============================================
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
#define KPLATFORM_WINDOWS 1
#ifndef _WIN64
#error "64-bit is required on Windows!"
#endif
#endif
// ============================================
// DLL 导入/导出宏
// ============================================
#ifdef KEXPORT
// 导出(编译引擎时)
#ifdef _MSC_VER
#define KAPI __declspec(dllexport)
#else
#define KAPI __attribute__((visibility("default")))
#endif
#else
// 导入(使用引擎时)
#ifdef _MSC_VER
#define KAPI __declspec(dllimport)
#else
#define KAPI
#endif
#endif
📖 代码详解
🔍 点击展开详细解释
| 代码行 | 说明 |
|---|---|
| 第 6-16 行 | 使用 typedef 定义固定大小的整数类型,确保跨平台一致性 |
| 第 21-22 行 | 定义浮点数类型,f32 用于大多数计算,f64 用于高精度场景 |
| 第 28-29 行 | 定义布尔类型,b8 节省内存,b32 性能更好(对齐) |
| 第 35-38 行 | 定义静态断言宏,用于编译时检查 |
| 第 44-51 行 | 使用静态断言验证类型大小,如果不符合预期,编译会失败 |
| 第 58-59 行 | 定义布尔常量 TRUE 和 FALSE |
| 第 65-70 行 | 检测是否在 Windows 64 位平台上编译,32位会报错 |
| 第 76-88 行 | 定义 KAPI 宏,用于标记需要从 DLL 导出或导入的函数 |
💡 为什么使用自定义类型?
问题:标准 C 类型大小不固定
int可能是 16/32 位long在 Windows 64位是 4字节,Linux 64位是 8字节- 跨平台开发时容易出错
解决方案:使用固定大小类型
u32在所有平台都是 4字节i64在所有平台都是 8字节- 代码更清晰,避免跨平台问题
🧪 步骤 4:创建测试代码
🎯 目标:编写最简单的引擎函数和测试程序,验证 DLL 机制
📄 引擎测试接口:engine/src/test.h
#pragma once
#include "defines.h"
// ============================================
// 测试函数声明
// ============================================
/**
* @brief 打印一个整数到控制台
* @param i 要打印的整数
*/
KAPI void print_int(i32 i);
📄 引擎测试实现:engine/src/test.c
#include "test.h"
#include <stdio.h>
// ============================================
// 测试函数实现
// ============================================
void print_int(i32 i) {
printf("The number is: %i\n", i); // 打印整数到控制台
}
📄 测试程序入口:testbed/src/main.c
#include <test.h>
// ============================================
// 测试程序主函数
// ============================================
int main(void) {
print_int(42); // 调用引擎函数,打印数字 42
return 0; // 程序正常退出
}
🔄 工作流程
┌───────────────────┐
│ engine.dll │ ← 编译引擎,导出 print_int() 函数
│ [KEXPORT 已定义] │
└─────────┬─────────┘
│
│ DLL 导出
▼
┌───────────────────┐
│ testbed.exe │ ← 编译测试程序,导入 print_int() 函数
│ [KEXPORT 未定义] │
└───────────────────┘
✅ 验证目标
这个简单的测试验证了以下机制:
- ✔️ DLL 导出/导入是否正常工作
- ✔️ 自定义类型(
i32)是否正确定义- ✔️ 引擎和测试程序能否正确链接
🔨 步骤 5:创建构建脚本
🎯 目标:自动化编译流程,一键构建整个项目
📄 引擎编译脚本:engine/build.bat
REM ============================================
REM Kohi Engine Build Script
REM ============================================
@ECHO OFF
SetLocal EnableDelayedExpansion
ECHO.
ECHO ========================================
ECHO Building Kohi Engine (DLL)
ECHO ========================================
ECHO.
REM 递归获取所有 .c 文件
SET cFilenames=
FOR /R %%f in (*.c) do (
SET cFilenames=!cFilenames! %%f
ECHO Found source file: %%f
)
REM 编译配置
SET assembly=engine
SET compilerFlags=-g -shared -Wvarargs -Wall -Werror
SET includeFlags=-Isrc -I%VULKAN_SDK%/Include
SET linkerFlags=-luser32 -lvulkan-1 -L%VULKAN_SDK%/Lib
SET defines=-D_DEBUG -DKEXPORT -D_CRT_SECURE_NO_WARNINGS
ECHO.
ECHO Building %assembly%.dll...
ECHO.
REM 执行编译
clang %cFilenames% %compilerFlags% -o ../bin/%assembly%.dll %defines% %includeFlags% %linkerFlags%
IF %ERRORLEVEL% EQU 0 (
ECHO.
ECHO [SUCCESS] Engine built successfully!
ECHO.
) ELSE (
ECHO.
ECHO [ERROR] Engine build failed!
ECHO.
)
📊 编译参数详解
| 参数 | 说明 | 作用 |
|---|---|---|
-g |
生成调试信息 | 允许使用调试器断点调试 |
-shared |
编译为共享库 | 生成 DLL 而不是 EXE |
-Wall |
启用所有警告 | 帮助发现潜在问题 |
-Werror |
警告视为错误 | 强制修复所有警告 |
-Isrc |
添加头文件搜索路径 | 允许使用 #include "defines.h" |
-DKEXPORT |
定义 KEXPORT 宏 | 使 KAPI 变为 __declspec(dllexport) |
-o ../bin/engine.dll |
指定输出文件 | 生成的 DLL 保存到 bin 目录 |
📄 测试程序编译脚本:testbed/build.bat
REM ============================================
REM Testbed Build Script
REM ============================================
@ECHO OFF
SetLocal EnableDelayedExpansion
ECHO.
ECHO ========================================
ECHO Building Testbed Application
ECHO ========================================
ECHO.
REM 递归获取所有 .c 文件
SET cFilenames=
FOR /R %%f in (*.c) do (
SET cFilenames=!cFilenames! %%f
ECHO Found source file: %%f
)
REM 编译配置
SET assembly=testbed
SET compilerFlags=-g
SET includeFlags=-Isrc -I../engine/src/
SET linkerFlags=-L../bin/ -lengine.lib
SET defines=-D_DEBUG -DKIMPORT
ECHO.
ECHO Building %assembly%.exe...
ECHO.
REM 执行编译
clang %cFilenames% %compilerFlags% -o ../bin/%assembly%.exe %defines% %includeFlags% %linkerFlags%
IF %ERRORLEVEL% EQU 0 (
ECHO.
ECHO [SUCCESS] Testbed built successfully!
ECHO.
) ELSE (
ECHO.
ECHO [ERROR] Testbed build failed!
ECHO.
)
📄 一键构建脚本:build-all.bat
@ECHO OFF
REM ============================================
REM Build All - Complete Project Build Script
REM ============================================
CLS
ECHO.
ECHO ============================================
ECHO KOHI ENGINE - BUILD ALL
ECHO ============================================
ECHO.
REM 先编译引擎 DLL
ECHO [STEP 1/2] Building Engine...
PUSHD engine
CALL build.bat
POPD
IF %ERRORLEVEL% NEQ 0 (
ECHO.
ECHO [FATAL ERROR] Engine build failed! Stopping build process.
ECHO.
EXIT /B %ERRORLEVEL%
)
REM 再编译测试程序
ECHO.
ECHO [STEP 2/2] Building Testbed...
PUSHD testbed
CALL build.bat
POPD
IF %ERRORLEVEL% NEQ 0 (
ECHO.
ECHO [FATAL ERROR] Testbed build failed!
ECHO.
EXIT /B %ERRORLEVEL%
)
ECHO.
ECHO ============================================
ECHO BUILD COMPLETE - All assemblies built!
ECHO ============================================
ECHO.
ECHO Output files:
ECHO - bin/engine.dll
ECHO - bin/engine.lib
ECHO - bin/testbed.exe
ECHO.
⚠️ 编译顺序很重要!
必须先编译引擎生成 DLL 和 LIB 文件,然后才能编译测试程序。
build-all.bat确保了正确的编译顺序,如果任何步骤失败,会立即停止。
⚙️ 步骤 6:配置 VS Code
🎯 目标:让 VS Code 理解项目结构,提供智能提示、编译、调试功能
📄 C/C++ 智能感知配置:.vscode/c_cpp_properties.json
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/engine/src/**",
"${VULKAN_SDK}/include"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"KEXPORT"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:\\Program Files\\LLVM\\bin\\clang.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
💡 配置说明
includePath: 告诉 IntelliSense 去哪里找头文件defines: 预定义宏,与编译时保持一致compilerPath: 编译器路径,用于解析代码
📄 构建任务配置:.vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "🔨 Build Engine",
"type": "shell",
"windows": {
"command": "${workspaceFolder}/engine/build.bat"
},
"options": {
"cwd": "${workspaceFolder}/engine"
},
"group": "build",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": ["$gcc"]
},
{
"label": "🎮 Build Testbed",
"type": "shell",
"windows": {
"command": "${workspaceFolder}/testbed/build.bat"
},
"options": {
"cwd": "${workspaceFolder}/testbed"
},
"group": "build",
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": ["$gcc"]
},
{
"label": "⚡ Build Everything",
"type": "shell",
"windows": {
"command": "${workspaceFolder}/build-all.bat"
},
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": ["$gcc"]
}
]
}
快捷键:
Ctrl+Shift+B→ 运行默认构建任务(Build Everything)Ctrl+Shift+P→ 输入 "Tasks: Run Task" → 选择特定任务
📄 调试配置:.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "🐛 Debug Testbed (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/testbed.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/bin/",
"environment": [],
"console": "newExternalWindow",
"preLaunchTask": "⚡ Build Everything"
}
]
}
使用方法:
- 在代码中设置断点(点击行号左侧)
- 按
F5启动调试 - 自动编译并运行,在断点处暂停
📄 编辑器设置:.vscode/settings.json
{
"C_Cpp.clang_format_style": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0 }",
"C_Cpp.clang_format_sortIncludes": false,
"editor.formatOnSave": true,
"editor.tabSize": 4,
"files.associations": {
"*.h": "c",
"*.c": "c"
},
"files.encoding": "utf8",
"files.eol": "\n"
}
📄 推荐扩展:.vscode/extensions.json
{
"recommendations": [
"ms-vscode.cpptools", // C/C++ 支持
"slevesque.shader", // Shader 语法高亮
"wayou.vscode-todo-highlight" // TODO 高亮
]
}
安装后 VS Code 会自动提示安装这些扩展。
🚀 步骤 7:编译和运行
🎯 目标:验证环境配置,成功编译并运行第一个程序
⚡ 编译项目
方法 1:命令行编译
# 在项目根目录运行
build-all.bat
方法 2:VS Code 快捷键
按 Ctrl+Shift+B
📺 预期编译输出
============================================
KOHI ENGINE - BUILD ALL
============================================
[STEP 1/2] Building Engine...
========================================
Building Kohi Engine (DLL)
========================================
Found source file: E:\Kohi\engine\src\test.c
Building engine.dll...
[SUCCESS] Engine built successfully!
[STEP 2/2] Building Testbed...
========================================
Building Testbed Application
========================================
Found source file: E:\Kohi\testbed\src\main.c
Building testbed.exe...
[SUCCESS] Testbed built successfully!
============================================
BUILD COMPLETE - All assemblies built!
============================================
Output files:
- bin/engine.dll
- bin/engine.lib
- bin/testbed.exe
▶️ 运行程序
方法 1:命令行运行
cd bin
testbed.exe
方法 2:VS Code 调试运行
按 F5(会自动编译并运行)
✅ 预期运行输出
The number is: 42
🎉 恭喜你!
如果你看到了 "The number is: 42",说明开发环境配置成功!
你已经完成了 Kohi 引擎的第一步,接下来可以开始添加更多功能了。
📊 深入理解
🔐 DLL 导出/导入机制
在 Windows 上,动态链接库(DLL)中的函数默认是不可见的。需要使用 __declspec(dllexport) 显式导出函数。
工作原理
| 📤 编译引擎时(导出) | 📥 使用引擎时(导入) |
|---|---|
作用:告诉编译器将此函数导出到 DLL |
作用:告诉编译器从 DLL 导入此函数 |
🎯 这种设计模式的优势
✅ 跨平台兼容:通过条件编译,可以在 Linux 上使用
__attribute__((visibility("default")))✅ 自动切换:同一个头文件,编译引擎时导出,使用引擎时导入 ✅ 行业标准:这是 Windows DLL 开发的标准做法
🤔 为什么编译成 DLL?
Kohi 引擎编译为 **DLL(动态链接库)**而不是静态库,原因如下:
| 🔥 热重载 | 📦 减小体积 | 🧩 模块化 |
|---|---|---|
|
游戏运行时可以重新加载引擎 DLL,无需重启整个程序 应用场景:实时修改引擎代码并立即看到效果 |
多个程序可以共享同一个 DLL,不需要每个程序都包含引擎代码 应用场景:多个游戏使用同一引擎 |
引擎和游戏代码分离,结构更清晰,便于维护 应用场景:团队协作开发 |
📜 批处理脚本技巧
🔄 延迟变量展开
SetLocal EnableDelayedExpansion
SET var=!var! newValue
为什么需要?
批处理默认在整行解析时展开变量。在循环中修改变量时,需要使用 !var! 而不是 %var%。
示例对比:
| ❌ 不使用延迟展开 | ✅ 使用延迟展开 |
|---|---|
结果: |
结果: |
⚠️ 错误处理
IF %ERRORLEVEL% NEQ 0 (
echo Error:%ERRORLEVEL% && exit
)
说明:
%ERRORLEVEL%:上一个命令的返回值(0 = 成功,非零 = 失败)NEQ 0:不等于 0&&:前一个命令成功才执行后一个命令
最佳实践:每个关键步骤后都检查错误,避免错误级联。
🎨 完整示例
示例:添加一个新的引擎函数
🎯 场景描述:
你想为引擎添加一个新的数学函数 add,用于计算两个整数的和。
📝 实现步骤
步骤 1:在头文件中声明函数
engine/src/test.h:
#pragma once
#include "defines.h"
KAPI void print_int(i32 i);
KAPI i32 add(i32 a, i32 b); // ✨ 新增函数声明
步骤 2:在源文件中实现函数
engine/src/test.c:
#include "test.h"
#include <stdio.h>
void print_int(i32 i) {
printf("The number is: %i\n", i);
}
// ✨ 新增函数实现
i32 add(i32 a, i32 b) {
return a + b; // 返回两数之和
}
步骤 3:在测试程序中使用
testbed/src/main.c:
#include <test.h>
int main(void) {
i32 result = add(10, 32); // ✨ 调用新函数
print_int(result); // 应该打印 42
return 0;
}
步骤 4:重新编译运行
build-all.bat
cd bin
testbed.exe
✅ 效果展示
The number is: 42
🎓 学习要点(点击展开)
- 声明与定义分离:头文件只包含声明,源文件包含实现
- 使用 KAPI 宏:确保函数能被 DLL 导出
- 自动编译:构建脚本会自动找到新的
.c文件,无需修改脚本 - 测试驱动:先在 testbed 中测试新功能,确保正确后再扩展
❓ 常见问题 FAQ
Q1: 编译时提示 "clang 不是内部或外部命令"?
| ❌ 问题原因 | Clang 没有正确安装或未添加到环境变量 |
| ✅ 解决方法 |
|
Q2: 链接时提示找不到 engine.lib?
| ❌ 问题原因 | 引擎还没有编译,或者编译失败了 |
| ✅ 解决方法 |
|
Q3: 为什么需要设置 VULKAN_SDK 环境变量?
| 💡 原因 | 虽然当前代码还没用到 Vulkan,但编译脚本中包含了 Vulkan 的头文件和库路径,为后续开发做准备 |
| 🔧 解决方案 |
方案 1(推荐):安装 Vulkan SDK,它会自动设置环境变量 方案 2(临时):暂时从
|
Q4: VS Code 的智能提示不工作?
| ❌ 可能原因 | C/C++ 扩展配置不正确,或扩展未安装 |
| ✅ 排查步骤 |
|
Q5: 运行时提示 "找不到 engine.dll"?
| ❌ 问题原因 | Windows 在运行时找不到 DLL 文件 |
| ✅ 解决方法 |
确保以下任一条件满足:
|
🐛 故障排查
快速诊断表
| 🔴 问题现象 | 🔍 可能原因 | ✅ 解决方法 |
|---|---|---|
编译报错:error: use of undeclared identifier 'u32' | 没有包含 defines.h | 在源文件顶部添加 #include "defines.h" |
| 运行时提示找不到 DLL | DLL 和 EXE 不在同一目录 | 确保 engine.dll 和 testbed.exe 都在 bin/ 目录下 |
编译警告:warning: implicit declaration of function | 函数声明未包含 | 检查头文件是否正确包含(#include "test.h") |
链接错误:unresolved external symbol | 函数定义缺失或链接设置错误 | 1. 确认函数已在 .c 文件中实现2. 检查链接器参数( -lengine.lib) |
编译错误:error: 'KAPI' undeclared | defines.h 未包含或宏定义错误 | 确保 #include "defines.h" 在文件顶部 |
| 运行没有任何输出 | 程序可能崩溃或输出被重定向 | 1. 使用调试器运行(F5) 2. 在代码中添加更多 printf 调试输出 |
🔧 调试技巧
💡 使用 VS Code 调试器(点击展开)
- 设置断点:点击代码行号左侧,出现红点
- 启动调试:按
F5 - 查看变量:鼠标悬停在变量上,或在左侧"变量"面板查看
- 单步执行:
F10:单步跳过(不进入函数内部)F11:单步进入(进入函数内部)Shift+F11:跳出当前函数
- 查看调用栈:左侧"调用堆栈"面板显示函数调用链
💡 使用 printf 调试(点击展开)
在关键位置添加调试输出:
#include <stdio.h>
int main(void) {
printf("[DEBUG] Program started\n");
i32 result = add(10, 32);
printf("[DEBUG] add(10, 32) = %i\n", result);
print_int(result);
printf("[DEBUG] Program exiting\n");
return 0;
}
✏️ 练习题
🎯 基础练习
练习 1:添加字符串打印函数
| 📝 任务 | 在引擎中添加一个 print_string 函数,接受一个字符串参数并打印 |
| 💡 提示 | 函数签名:KAPI void print_string(const char* str); |
| ✅ 预期结果 | 能够在 testbed 中打印 "Hello, Kohi!" |
🔍 点击查看答案
engine/src/test.h:
#pragma once
#include "defines.h"
KAPI void print_int(i32 i);
KAPI void print_string(const char* str); // ✨ 新增
engine/src/test.c:
#include "test.h"
#include <stdio.h>
void print_int(i32 i) {
printf("The number is: %i\n", i);
}
void print_string(const char* str) {
printf("%s\n", str); // 打印字符串
}
testbed/src/main.c:
#include <test.h>
int main(void) {
print_string("Hello, Kohi!");
print_int(42);
return 0;
}
运行结果:
Hello, Kohi!
The number is: 42
练习 2:添加 .gitignore
| 📝 任务 | 创建 .gitignore 文件,忽略编译输出目录 |
| 💡 提示 | 需要忽略 bin/ 目录 |
| ✅ 预期结果 | git status 时不显示 bin/ 目录下的文件 |
🔍 点击查看答案
在项目根目录创建 .gitignore 文件,内容为:
# 编译输出目录
bin/
# VS Code 临时文件
.vscode/.browse.VC.db
.vscode/.browse.VC.opendb
# Windows 临时文件
*.tmp
*.log
要点说明:
- 这会忽略所有编译生成的文件
- 避免将二进制文件提交到版本控制系统
- 保持仓库整洁,加快 Git 操作速度
验证:
git status
# 应该看不到 bin/ 目录下的文件
🚀 进阶挑战
挑战 1:实现一个简单的数学库
| 📝 要求 | 创建 math_utils.h 和 math_utils.c,实现加减乘除四个函数 |
| 💡 提示 | 函数签名如 KAPI i32 math_add(i32 a, i32 b); |
| ⚠️ 注意 | 除法函数需要处理除零错误 |
| ✅ 验证 | 在 testbed 中测试所有函数 |
🔍 点击查看答案
engine/src/math_utils.h:
#pragma once
#include "defines.h"
/**
* @brief 数学工具库
* 提供基本的整数运算功能
*/
KAPI i32 math_add(i32 a, i32 b);
KAPI i32 math_subtract(i32 a, i32 b);
KAPI i32 math_multiply(i32 a, i32 b);
KAPI i32 math_divide(i32 a, i32 b);
engine/src/math_utils.c:
#include "math_utils.h"
#include <stdio.h>
i32 math_add(i32 a, i32 b) {
return a + b;
}
i32 math_subtract(i32 a, i32 b) {
return a - b;
}
i32 math_multiply(i32 a, i32 b) {
return a * b;
}
i32 math_divide(i32 a, i32 b) {
if (b == 0) {
printf("[ERROR] Division by zero!\n");
return 0; // 返回 0 作为错误值
}
return a / b;
}
testbed/src/main.c:
#include <test.h>
#include <math_utils.h>
int main(void) {
print_string("=== Math Utils Test ===");
print_int(math_add(10, 5)); // 15
print_int(math_subtract(10, 5)); // 5
print_int(math_multiply(10, 5)); // 50
print_int(math_divide(10, 5)); // 2
print_int(math_divide(10, 0)); // 错误:除零
return 0;
}
运行结果:
=== Math Utils Test ===
The number is: 15
The number is: 5
The number is: 50
The number is: 2
[ERROR] Division by zero!
The number is: 0
要点说明:
- ✅ 编译脚本会自动找到新的
.c文件,无需修改脚本 - ✅ 记得在 testbed 中包含新的头文件
- ✅ 除法函数添加了零检查以防崩溃
- ✅ 使用了 Doxygen 风格的注释,便于生成文档
挑战 2:实现类型大小查询函数
| 📝 要求 | 实现一个 print_type_sizes() 函数,打印所有自定义类型的大小 |
| 💡 提示 | 使用 sizeof() 运算符 |
| ✅ 预期输出 |
|
🔍 点击查看答案
engine/src/test.h:
#pragma once
#include "defines.h"
KAPI void print_int(i32 i);
KAPI void print_type_sizes(void); // ✨ 新增
engine/src/test.c:
#include "test.h"
#include <stdio.h>
void print_int(i32 i) {
printf("The number is: %i\n", i);
}
void print_type_sizes(void) {
printf("=== Type Sizes ===\n");
printf("u8: %zu byte(s)\n", sizeof(u8));
printf("u16: %zu byte(s)\n", sizeof(u16));
printf("u32: %zu byte(s)\n", sizeof(u32));
printf("u64: %zu byte(s)\n", sizeof(u64));
printf("\n");
printf("i8: %zu byte(s)\n", sizeof(i8));
printf("i16: %zu byte(s)\n", sizeof(i16));
printf("i32: %zu byte(s)\n", sizeof(i32));
printf("i64: %zu byte(s)\n", sizeof(i64));
printf("\n");
printf("f32: %zu byte(s)\n", sizeof(f32));
printf("f64: %zu byte(s)\n", sizeof(f64));
printf("\n");
printf("b8: %zu byte(s)\n", sizeof(b8));
printf("b32: %zu byte(s)\n", sizeof(b32));
printf("==================\n");
}
testbed/src/main.c:
#include <test.h>
int main(void) {
print_type_sizes();
return 0;
}
🔗 相关资源
📚 Kohi 引擎资源
| 资源 | 链接 | 说明 |
|---|---|---|
| 🏠 GitHub 仓库 | travisvroman/kohi | 源代码和最新更新 |
| 📖 Wiki 文档 | Wiki | 详细的引擎文档 |
| 🎥 视频教程 | YouTube 系列 | 作者的视频教程 |
📖 扩展阅读
| 主题 | 资源 | 说明 |
|---|---|---|
| 🔧 编译器 | Clang 文档 | 了解更多编译器选项和优化 |
| 📦 DLL 开发 | 微软官方文档 | 深入理解 Windows DLL 机制 |
| 🖥️ VS Code | C/C++ 配置指南 | VS Code C/C++ 开发配置 |
| 🎮 游戏引擎 | 引擎架构 | 游戏引擎设计原理 |
| 🔨 构建系统 | CMake 文档 | 更强大的跨平台构建工具 |
🚀 下一步学习
graph LR
A[当前:Windows环境配置] --> B[02 - Linux环境配置]
A --> C[03 - 日志系统实现]
A --> D[04 - 内存管理]
B --> E[05 - 平台抽象层]
C --> E
D --> E
style A fill:#c8e6c9
style B fill:#fff9c4
style C fill:#fff9c4
style D fill:#fff9c4
style E fill:#ffccbc
推荐学习路径:
-
📌 必学:
- 02 - Linux 环境配置 - 实现跨平台开发
- 03 - 日志系统实现 - 为引擎添加日志功能
- 04 - 内存管理 - 自定义内存分配器
-
⭐ 进阶:
📝 总结
🎉 恭喜你完成了第一步!
本节内容回顾:
✅ 你学会了
|
⚡ 核心要点
|
🛤️ 你现在的位置
✅ Windows 环境配置(当前)
│
├─→ Linux 环境配置
├─→ 日志系统
├─→ 断言和错误处理
├─→ 内存管理
└─→ 平台抽象层
└─→ 窗口系统
└─→ 输入处理
└─→ 渲染系统...
通过这次配置,你建立了一个可扩展的引擎开发框架。在后续章节中,我们将在这个基础上添加更多功能,如日志系统、内存管理、平台抽象层、渲染系统等。
📌 重要提示
本教程是一个系列教程的第一部分。每个教程都会在前一个的基础上构建新功能。建议按顺序学习,这样才能理解整个引擎的设计思路。
📅 本教程更新时间:2025-01-18
🏷️ Kohi 引擎版本:v0.1.0 (Initial Setup)
📖 参考提交:06fcdd94c82d5871bae6a2883850f53138b85f20
📖 关注公众号
关注我,领取章节视频教程
💖 支持作者
如果这篇教程对你有帮助,欢迎请作者喝杯咖啡 ☕
您的支持是我持续创作的动力!
感谢每一位支持者!🙏
📅 最后更新:2025-11-19 ✍️ 作者:上手实验室