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

如何打破C++项目中Main.exe与Framework.dll的循环依赖

打破C++模块循环依赖的落地方案

核心目标:保留Main.exe对Framework生命周期类的使用体验,构建Main.exe时无需链接Framework.lib,彻底解除二者构建时的循环依赖。


推荐方案:抽象公共接口 + 运行时动态加载

这个方案改动量极小,兼容性最好,完全不影响现有插件模块的逻辑。

实现思路

把Main.exe依赖的Framework生命周期能力抽成纯虚抽象接口,放在公共头文件中供两个模块引用;Framework.dll内部继承该接口实现原有逻辑,对外导出C风格的工厂/销毁函数;Main.exe通过Windows API运行时动态加载Framework.dll,拿到工厂函数创建接口实例使用,全程不需要链接Framework的导入库。

具体代码实现

  1. 新增公共头文件IFramework.h,由Main.exe和Framework.dll共同包含,无任何模块绑定:
#pragma once
// 纯虚接口,只定义能力约定,不包含实现
class IFramework {
public:
    virtual ~IFramework() = default;
    virtual void Init() = 0;
    virtual void Begin() = 0;
    virtual void End() = 0;
    virtual void Shutdown() = 0;
};

// 约定导出函数的签名,用extern "C"避免C++名字修饰问题
extern "C" {
    using FnCreateFramework = IFramework* (*)();
    using FnDestroyFramework = void (*)(IFramework*);
}
  1. 修改Framework.dll代码,原有Framework类逻辑几乎不用改动,只要继承接口、导出工厂函数即可:
#include "IFramework.h"

// 原有Framework类全部逻辑保留,仅添加接口继承
class Framework : public IFramework {
public:
    void Init() override { /* 原有Init逻辑不变 */ }
    void Begin() override { /* 原有Begin逻辑不变 */ }
    void End() override { /* 原有End逻辑不变 */ }
    void Shutdown() override { /* 原有Shutdown逻辑不变 */ }

private:
    // 原有所有成员变量、私有方法完全保留
};

// 导出C风格工厂函数,供外部动态加载调用
extern "C" __declspec(dllexport) IFramework* CreateFramework() {
    return new Framework();
}
extern "C" __declspec(dllexport) void DestroyFramework(IFramework* inst) {
    delete inst;
}

注意:对象的创建、销毁逻辑全部放在Framework.dll内部,不会出现跨模块堆分配释放不匹配的问题。

  1. 修改Main.exe代码,移除对Framework.hpp的引用和Framework.lib的链接,改为运行时动态加载:
#include "IFramework.h"
#include <windows.h>

// 可以封装RAII持有类,对齐原有栈对象的使用体验
struct FrameworkGuard {
    HMODULE hFwDll = nullptr;
    IFramework* fwInst = nullptr;
    FnDestroyFramework destroyFn = nullptr;

    FrameworkGuard() {
        hFwDll = LoadLibraryW(L"Framework.dll");
        if (!hFwDll) { /* 加载失败处理,比如弹提示退出 */ }

        auto createFn = reinterpret_cast<FnCreateFramework>(
            GetProcAddress(hFwDll, "CreateFramework")
        );
        destroyFn = reinterpret_cast<FnDestroyFramework>(
            GetProcAddress(hFwDll, "DestroyFramework")
        );
        if (!createFn || !destroyFn) { /* 导出函数缺失处理 */ }

        fwInst = createFn();
    }

    ~FrameworkGuard() {
        if (fwInst && destroyFn) destroyFn(fwInst);
        if (hFwDll) FreeLibrary(hFwDll);
    }

    IFramework* operator->() const noexcept {
        return fwInst;
    }

    // 禁止拷贝,避免重复释放
    FrameworkGuard(const FrameworkGuard&) = delete;
    FrameworkGuard& operator=(const FrameworkGuard&) = delete;
};

int main() {
    FrameworkGuard fw;
    fw->Init();
    
    while (Quit() == false) {
        fw->Begin();
        DoWork();
        fw->End();
        MoreWork();
    }
    
    fw->Shutdown();
    return 0;
}

方案优势

  • 彻底解除构建时循环依赖:构建顺序变为「编译Main.exe生成Main.lib→ 链接Main.lib编译Framework.dll → 生成最终Main.exe」,全程不需要Framework.lib参与Main.exe的链接过程。
  • 改动量极小:Framework类的核心逻辑、所有插件模块的原有代码完全不需要修改,不影响现有业务。
  • 使用体验对齐原有代码:通过RAII封装后,上层调用逻辑和原本栈上创建Framework对象的写法几乎一致,没有额外的使用成本。
  • 兼容性强:C风格导出函数不会受C++名字修饰规则影响,不会出现不同编译选项下找不到导出符号的问题。

其他可选方案(不推荐)

  • 拆分Framework.dll的生命周期类为独立静态库:把Main.exe用到的Framework类单独抽成小静态库同时链接给Main和Framework,但是会造成代码冗余,且如果这个类依赖Framework内部其他逻辑,拆分成本极高。
  • 使用延迟加载DLL:本质还是需要链接Framework.lib,无法解决构建时的循环依赖问题,需要额外做lib的手工导入导出处理,复杂度远高于动态加载方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 05:51:14