You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中Raylib结合热重载的链接问题(Windows+Linux平台)

问题:Raylib热重载动态库触发内存访问违规崩溃

场景概述

刚接触C++,基于Raylib开发游戏,尝试将核心逻辑拆分到动态库实现热重载:主程序加载动态库并调用其接口,动态库仅实现绘制矩形和帧率显示功能。项目编译链接正常,但运行时窗口短暂打开后崩溃,崩溃点在调用动态库中的UpdateGame函数时,触发内存访问违规(指向Raylib内部RLGL指针),且此时IsWindowReady()返回false,怀疑Raylib被重复加载。

相关代码

main.cpp

#include "raylib.h"
#include "platform.cpp"

int screenWidth = 1920;
int screenHeight = 1080;

int main() {
    GameAPI gameAPI = {};
    GameLib gameLib = LoadGameLibrary(&gameAPI);
    if (gameLib == nullptr) {
        TraceLog(LOG_ERROR, "Could not load game API");
        return -1;
    }
    Game* game = gameAPI.createGame();
    if (!game) {
        TraceLog(LOG_ERROR, "Could not create game!");
        FreeGameLibrary(gameLib);
        return -1;
    }
    InitWindow(screenWidth, screenHeight, "Raylib");
  
    // Main game loop
    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(WHITE);
        if (IsWindowReady()) {
          gameAPI.updateGame(game);
        } else {
          TraceLog(LOG_ERROR, "Window not ready!");
        }
        EndDrawing();
    }

    gameAPI.destroyGame(game);
    FreeGameLibrary(gameLib);
}

platform.hpp & platform.cpp

// platform.hpp
#ifdef _WIN32
    #define NOGDI             // All GDI defines and routines
    #define NOUSER            // All USER defines and routines
    #undef near
    #undef far

    #define WIN32_LEAN_AND_MEAN
    #include "Windows.h"
    typedef HMODULE GameLib;
#else
    #include <dlfcn.h>
    typedef void* GameLib;
#endif

#include "game.hpp"

typedef Game* (*CreateGameFunc)();
typedef void (*UpdateGameFunc)(Game*);
typedef void (*DestroyGameFunc)(Game*);

struct GameAPI {
  CreateGameFunc createGame;
  UpdateGameFunc updateGame;
  DestroyGameFunc destroyGame;
};

GameLib LoadGameLibrary(GameAPI* gameAPI);
void FreeGameLibrary(void* gameLib);

// platform.cpp
#include "platform.hpp"
#ifdef _WIN32
GameLib LoadGameLibrary(GameAPI* gameAPI) {
    GameLib gameLib = (GameLib) LoadLibraryA("game.dll");
    if (!gameLib) {
        // Handle error
        return nullptr;
    }
  
    gameAPI->createGame = (CreateGameFunc)GetProcAddress(gameLib, "CreateGame");
    gameAPI->updateGame = (UpdateGameFunc)GetProcAddress(gameLib, "UpdateGame");
    gameAPI->destroyGame = (DestroyGameFunc)GetProcAddress(gameLib, "DestroyGame");
    if (!gameAPI->createGame || !gameAPI->updateGame || !gameAPI->destroyGame) {
      return nullptr;
    } else {
      return gameLib;
    }
}

void FreeGameLibrary(GameLib gameLib) {
    FreeLibrary(gameLib);
}
#endif

game.hpp & game.cpp

// game.hpp
#ifndef GAME_HPP
#define GAME_HPP

#define EXTERNAL extern "C"

#ifdef _WIN32
  #ifdef GAME_EXPORTS
    #define GAME_API __declspec(dllexport)
  #else
    #define GAME_API __declspec(dllimport)
  #endif
#else
  #define GAME_API
#endif

EXTERNAL class GAME_API Game {
public:
    void Update();
};

EXTERNAL GAME_API Game* CreateGame();
EXTERNAL GAME_API void DestroyGame(Game* game);
EXTERNAL GAME_API void UpdateGame(Game* game);
#endif // GAME_HPP

// game.cpp
void Game::Update() {
    // Update logic that uses Raylib functions
    // At this point IsWindowReady() return false for some reason?
    DrawFPS(10, 10);
    DrawRectangle(100, 100, 200, 200, GREEN);
}
// Macro to export using __declspec(dllexport) extern "C"
EXTERNAL void UpdateGame(Game *game) {
    game->Update();
}

// 其他CreateGame/DestroyGame实现(无崩溃风险,略)

CMakeLists.txt

cmake_minimum_required(VERSION 3.24) # FetchContent is available in 3.11+
project(Raylib)

if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
  error("MacOS is not supported")
endif()

# Generate compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") # works
set(CMAKE_CXX_STANDARD 20)
# Dependencies
set(RAYLIB_VERSION 5.0)
find_package(raylib ${RAYLIB_VERSION} QUIET) # QUIET or REQUIRED
if (NOT raylib_FOUND) # If there's none, fetch and build raylib
  include(FetchContent)
  FetchContent_Declare(
    raylib
    DOWNLOAD_EXTRACT_TIMESTAMP OFF
    URL https://github.com/raysan5/raylib/archive/refs/tags/${RAYLIB_VERSION}.tar.gz
  )
  FetchContent_GetProperties(raylib)
  if (NOT raylib_POPULATED) # Have we downloaded raylib yet?
    set(FETCHCONTENT_QUIET YES)
    FetchContent_Populate(raylib)
    set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
    add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR})
  endif()
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I\"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/um\"")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I\"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/shared\"")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I\"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/ucrt\"")
  if (NOT EXISTS "${CMAKE_BINARY_DIR}/raylib.dll")
    file(COPY ${raylib_BINARY_DIR}/raylib/raylib.dll DESTINATION ${CMAKE_BINARY_DIR})
  endif()
else()
  if (NOT EXISTS "${CMAKE_BINARY_DIR}/raylib.so")
    file(COPY ${raylib_BINARY_DIR}/raylib/raylib.so DESTINATION ${CMAKE_BINARY_DIR})
  endif()
endif()

# Project
add_executable(${PROJECT_NAME} ./src/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE raylib)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

# Game dynamic library for hot reloading
add_library(game SHARED ./src/game.cpp)
target_compile_definitions(game PRIVATE GAME_EXPORTS)
target_link_libraries(game PRIVATE raylib)
# Custom target to build only the game library
add_custom_target(game_only
  DEPENDS game
  COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target game
)

解决方案

1. 修正extern "C"的错误使用

extern "C"不能修饰C类,这会导致符号混乱,类的成员函数无法以C链接方式导出。需将C类包装为C风格接口:

// 修改后的game.hpp
#ifndef GAME_HPP
#define GAME_HPP

#ifdef _WIN32
  #ifdef GAME_EXPORTS
    #define GAME_API __declspec(dllexport)
  #else
    #define GAME_API __declspec(dllimport)
  #endif
#else
  #define GAME_API
#endif

// 仅用extern "C"包裹C风格导出函数
extern "C" {
    // 前向声明,隐藏类内部细节
    typedef struct Game Game;

    GAME_API Game* CreateGame();
    GAME_API void DestroyGame(Game* game);
    GAME_API void UpdateGame(Game* game);
}

// 内部C++类,不对外导出
class Game {
public:
    void Update();
};

#endif // GAME_HPP

2. 调整Raylib初始化与动态库加载顺序

当前先加载动态库再初始化Raylib,动态库加载时若触发静态初始化,会在Raylib全局状态就绪前访问其资源,导致状态异常。需先初始化窗口,再加载动态库:

// 修改后的main.cpp核心逻辑
int main() {
    // 先初始化Raylib窗口
    InitWindow(screenWidth, screenHeight, "Raylib");

    GameAPI gameAPI = {};
    GameLib gameLib = LoadGameLibrary(&gameAPI);
    if (gameLib == nullptr) {
        TraceLog(LOG_ERROR, "Could not load game API");
        CloseWindow(); // 新增:失败时关闭窗口
        return -1;
    }
    Game* game = gameAPI.createGame();
    if (!game) {
        TraceLog(LOG_ERROR, "Could not create game!");
        FreeGameLibrary(gameLib);
        CloseWindow(); // 新增:失败时关闭窗口
        return -1;
    }
  
    // 主循环逻辑不变...

    gameAPI.destroyGame(game);
    FreeGameLibrary(gameLib);
    CloseWindow(); // 新增:退出前关闭窗口
}

3. 优化CMake配置

  • 移除Windows硬编码头文件路径,CMake会自动处理Windows SDK依赖
  • 使用add_custom_command自动同步Raylib共享库,避免手动复制遗漏:
// 替换原Windows/Linux库复制逻辑
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:raylib>
        $<TARGET_FILE_DIR:${PROJECT_NAME}>
    )
    add_custom_command(TARGET game POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:raylib>
        $<TARGET_FILE_DIR:game>
    )
else()
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:raylib>
        $<TARGET_FILE_DIR:${PROJECT_NAME}>
    )
    add_custom_command(TARGET game POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:raylib>
        $<TARGET_FILE_DIR:game>
    )
endif()

4. 确保Raylib全局状态共享

动态库和主程序必须使用同一实例的Raylib共享库,CMake中已设置BUILD_SHARED_LIBS ON,需保证编译时动态库与主程序链接的Raylib版本完全一致,避免符号冲突。

核心原因总结

崩溃的本质是:动态库加载时机过早,导致其Raylib函数未关联到主程序初始化的全局状态;同时extern "C"错误修饰C++类,引发符号链接异常,最终导致访问无效的RLGL指针。通过调整加载顺序、修正接口定义、优化CMake配置,即可解决问题。

内容的提问来源于stack exchange,提问作者Archie Murray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 09:45:54