教程01 - Windows 环境初始配置

42 阅读25分钟

📖 本节导读

🎯 学习目标

学完本节后,你将掌握如何在 Windows 平台上搭建 Kohi 引擎的开发环境,理解项目的基本结构,并成功编译运行第一个测试程序。

⏱️ 预计时间:20 分钟 💪 难度等级:⭐☆☆☆☆(入门级) 🏷️ 关键词环境搭建 DLL开发 VS Code配置 Clang编译

📑 目录导航(点击展开)

📋 前置知识

学习本节内容前,你需要了解:

✅ 必备知识

  • 基本的 C 语言知识
  • Windows 命令行基本操作
  • 了解什么是编译器和链接器

💡 加分项

  • Git 版本控制基础
  • DLL 动态链接库概念
  • VS Code 使用经验

💡 学习建议 如果你还不熟悉这些概念,建议先学习 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)的工作环境提供代码补全、调试等功能
📦 构建系统创建编译脚本来自动化构建流程一键编译,提高开发效率
📁 项目结构划分引擎代码和测试代码的目录结构代码组织清晰,易于维护
💻 基础代码编写最小可运行的代码来验证环境确保环境配置正确

🤔 为什么需要它?

❌ 没有标准配置时

  • 😫 编译困难 每次都要手动输入复杂的编译命令

  • 😵 环境不一致 团队成员使用不同的编译器设置导致问题

  • 🐛 调试困难 无法使用调试器进行断点调试

  • 🔍 代码提示缺失 智能补全和跳转功能无法工作

✅ 有了标准配置后

  • 一键编译 Ctrl+Shift+B 即可编译整个项目

  • 🤝 环境一致 团队成员拥有相同的开发环境

  • 🔧 调试便捷 可以使用 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_WINDOWSWindows 平台标识条件编译 Windows 特定代码
KPLATFORM_LINUXLinux 平台标识条件编译 Linux 特定代码
KAPIDLL 导入/导出宏标记跨 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 中,这样才能在命令行中直接使用。

设置方法

  1. 右键"此电脑" → 属性 → 高级系统设置
  2. 环境变量 → 系统变量 → Path → 编辑
  3. 新建 → 添加 C:\Program Files\LLVM\bin
  4. 确定 → 重启命令行窗口

📁 步骤 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 行定义布尔常量 TRUEFALSE
第 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"
        }
    ]
}

使用方法

  1. 在代码中设置断点(点击行号左侧)
  2. F5 启动调试
  3. 自动编译并运行,在断点处暂停

📄 编辑器设置:.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) 显式导出函数。

工作原理

📤 编译引擎时(导出) 📥 使用引擎时(导入)
// 定义了 KEXPORT
#define KAPI __declspec(dllexport)

KAPI void print_int(i32 i);
// 展开为:
// __declspec(dllexport) void print_int(i32 i);

作用:告诉编译器将此函数导出到 DLL

// 未定义 KEXPORT
#define KAPI __declspec(dllimport)

KAPI void print_int(i32 i);
// 展开为:
// __declspec(dllimport) void print_int(i32 i);

作用:告诉编译器从 DLL 导入此函数

🎯 这种设计模式的优势

跨平台兼容:通过条件编译,可以在 Linux 上使用 __attribute__((visibility("default")))自动切换:同一个头文件,编译引擎时导出,使用引擎时导入 ✅ 行业标准:这是 Windows DLL 开发的标准做法


🤔 为什么编译成 DLL?

Kohi 引擎编译为 **DLL(动态链接库)**而不是静态库,原因如下:

🔥 热重载 📦 减小体积 🧩 模块化
游戏运行时可以重新加载引擎 DLL,无需重启整个程序

应用场景:实时修改引擎代码并立即看到效果
多个程序可以共享同一个 DLL,不需要每个程序都包含引擎代码

应用场景:多个游戏使用同一引擎
引擎和游戏代码分离,结构更清晰,便于维护

应用场景:团队协作开发

📜 批处理脚本技巧

🔄 延迟变量展开

SetLocal EnableDelayedExpansion
SET var=!var! newValue

为什么需要?

批处理默认在整行解析时展开变量。在循环中修改变量时,需要使用 !var! 而不是 %var%

示例对比

❌ 不使用延迟展开 ✅ 使用延迟展开
SET count=0
FOR %%f in (*.c) do (
    SET count=%count% + 1
)
ECHO %count%

结果count 始终为 0

SetLocal EnableDelayedExpansion
SET count=0
FOR %%f in (*.c) do (
    SET count=!count! + 1
)
ECHO !count!

结果count 正确累加


⚠️ 错误处理

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

🎓 学习要点(点击展开)
  1. 声明与定义分离:头文件只包含声明,源文件包含实现
  2. 使用 KAPI 宏:确保函数能被 DLL 导出
  3. 自动编译:构建脚本会自动找到新的 .c 文件,无需修改脚本
  4. 测试驱动:先在 testbed 中测试新功能,确保正确后再扩展

❓ 常见问题 FAQ

Q1: 编译时提示 "clang 不是内部或外部命令"?

❌ 问题原因 Clang 没有正确安装或未添加到环境变量
✅ 解决方法
  1. 确认已安装 LLVM/Clang
  2. 将 Clang 安装目录(例如 C:\Program Files\LLVM\bin)添加到系统环境变量 PATH
  3. 重新打开命令行窗口(必须重启才能生效)
  4. 运行 clang --version 验证

Q2: 链接时提示找不到 engine.lib

❌ 问题原因 引擎还没有编译,或者编译失败了
✅ 解决方法
  1. 先单独编译引擎:cd engine && build.bat
  2. 检查 bin/ 目录下是否有 engine.dllengine.lib
  3. 确认引擎编译没有错误(查看编译输出)
  4. 再编译测试程序

Q3: 为什么需要设置 VULKAN_SDK 环境变量?

💡 原因 虽然当前代码还没用到 Vulkan,但编译脚本中包含了 Vulkan 的头文件和库路径,为后续开发做准备
🔧 解决方案

方案 1(推荐):安装 Vulkan SDK,它会自动设置环境变量

方案 2(临时):暂时从 engine/build.bat 中删除与 Vulkan 相关的行:

REM SET includeFlags=-Isrc -I%VULKAN_SDK%/Include
REM SET linkerFlags=-luser32 -lvulkan-1 -L%VULKAN_SDK%/Lib

SET includeFlags=-Isrc
SET linkerFlags=-luser32

Q4: VS Code 的智能提示不工作?

❌ 可能原因 C/C++ 扩展配置不正确,或扩展未安装
✅ 排查步骤
  1. 确认已安装 "C/C++" 扩展(ms-vscode.cpptools
  2. 检查 .vscode/c_cpp_properties.json 中的路径是否正确
  3. Ctrl+Shift+P,运行 "C/C++: Edit Configurations (UI)" 检查配置
  4. 查看 VS Code 输出面板(Output)中的 C/C++ 日志
  5. 重启 VS Code

Q5: 运行时提示 "找不到 engine.dll"?

❌ 问题原因 Windows 在运行时找不到 DLL 文件
✅ 解决方法

确保以下任一条件满足

  1. 方法 1(推荐):DLL 和 EXE 在同一目录 确保 engine.dlltestbed.exe 都在 bin/ 目录下

  2. 方法 2:DLL 在系统 PATH 中 将 bin/ 目录添加到环境变量 PATH

  3. 方法 3:在 DLL 所在目录运行 cd bin && testbed.exe


🐛 故障排查

快速诊断表

🔴 问题现象🔍 可能原因✅ 解决方法
编译报错:error: use of undeclared identifier 'u32'没有包含 defines.h在源文件顶部添加 #include "defines.h"
运行时提示找不到 DLLDLL 和 EXE 不在同一目录确保 engine.dlltestbed.exe 都在 bin/ 目录下
编译警告:warning: implicit declaration of function函数声明未包含检查头文件是否正确包含(#include "test.h"
链接错误:unresolved external symbol函数定义缺失或链接设置错误1. 确认函数已在 .c 文件中实现
2. 检查链接器参数(-lengine.lib
编译错误:error: 'KAPI' undeclareddefines.h 未包含或宏定义错误确保 #include "defines.h" 在文件顶部
运行没有任何输出程序可能崩溃或输出被重定向1. 使用调试器运行(F5)
2. 在代码中添加更多 printf 调试输出

🔧 调试技巧

💡 使用 VS Code 调试器(点击展开)
  1. 设置断点:点击代码行号左侧,出现红点
  2. 启动调试:按 F5
  3. 查看变量:鼠标悬停在变量上,或在左侧"变量"面板查看
  4. 单步执行
    • F10:单步跳过(不进入函数内部)
    • F11:单步进入(进入函数内部)
    • Shift+F11:跳出当前函数
  5. 查看调用栈:左侧"调用堆栈"面板显示函数调用链
💡 使用 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.hmath_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() 运算符
✅ 预期输出
=== Type Sizes ===
u8:  1 byte(s)
u16: 2 byte(s)
u32: 4 byte(s)
...
🔍 点击查看答案

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

推荐学习路径

  1. 📌 必学

  2. ⭐ 进阶


📝 总结

🎉 恭喜你完成了第一步!

本节内容回顾

✅ 你学会了

  • ✔️ 在 Windows 上搭建 Kohi 引擎开发环境
  • ✔️ 理解项目的基本结构:引擎 DLL + 测试程序
  • ✔️ 掌握基础类型系统和平台检测机制
  • ✔️ 能够编译和运行第一个测试程序
  • ✔️ 配置 VS Code 进行开发和调试

⚡ 核心要点

  • 🔑 DLL 导出/导入机制
  • 🔑 批处理构建脚本自动化
  • 🔑 固定大小类型保证跨平台一致性
  • 🔑 VS Code 配置提升开发效率
  • 🔑 模块化设计(引擎 + 测试分离)

🛤️ 你现在的位置

✅ Windows 环境配置(当前)
    │
    ├─→ Linux 环境配置
    ├─→ 日志系统
    ├─→ 断言和错误处理
    ├─→ 内存管理
    └─→ 平台抽象层
        └─→ 窗口系统
            └─→ 输入处理
                └─→ 渲染系统...

通过这次配置,你建立了一个可扩展的引擎开发框架。在后续章节中,我们将在这个基础上添加更多功能,如日志系统、内存管理、平台抽象层、渲染系统等。


📌 重要提示

本教程是一个系列教程的第一部分。每个教程都会在前一个的基础上构建新功能。建议按顺序学习,这样才能理解整个引擎的设计思路。


📅 本教程更新时间:2025-01-18 🏷️ Kohi 引擎版本:v0.1.0 (Initial Setup) 📖 参考提交06fcdd94c82d5871bae6a2883850f53138b85f20


📖 关注公众号

上手实验室公众号

关注我,领取章节视频教程


💖 支持作者

如果这篇教程对你有帮助,欢迎请作者喝杯咖啡 ☕

打赏二维码

您的支持是我持续创作的动力!

感谢每一位支持者!🙏

📅 最后更新:2025-11-19 ✍️ 作者:上手实验室