如何在C++中通过显式链接与依赖注入动态扩展图形计算程序
实现步骤
1. 确保IShape接口可跨模块访问
首先你的app.exe中的IShape接口必须被导出,这样ellipse.dll才能继承它。修改接口头文件,添加导出宏:
// ShapeInterface.h #ifdef SHAPE_EXPORTS #define SHAPE_API __declspec(dllexport) #else #define SHAPE_API __declspec(dllimport) #endif class SHAPE_API IShape { public: virtual ~IShape() = default; virtual double getArea() const = 0; virtual double getPerimeter() const = 0; virtual const char* getName() const = 0; // 新增销毁函数,避免跨模块内存释放问题 virtual void destroy() { delete this; } };
编译app.exe时,定义SHAPE_EXPORTS宏(VS中可在项目属性->C/C++->预处理器->预处理器定义中添加),编译器会生成app.lib导出库,后续ellipse.dll需要链接这个库。
2. 编写ellipse.dll代码
创建DLL项目,包含ShapeInterface.h并链接app.lib。实现Ellipse类,并导出C风格的工厂函数(避免C++名字修饰):
// Ellipse.cpp #include "ShapeInterface.h" #include <cmath> class Ellipse : public IShape { private: double majorAxis, minorAxis; public: Ellipse(double major, double minor) : majorAxis(major), minorAxis(minor) {} double getArea() const override { return 3.1415926 * majorAxis * minorAxis; } double getPerimeter() const override { // 椭圆周长近似公式 return 3.1415926 * (3*(majorAxis + minorAxis) - sqrt((3*majorAxis + minorAxis)*(majorAxis + 3*minorAxis))); } const char* getName() const override { return "Ellipse"; } }; // 导出创建椭圆的函数,统一命名为CreateShape extern "C" SHAPE_API IShape* CreateShape(double major, double minor) { return new Ellipse(major, minor); } // 导出图形名称,供主程序识别 extern "C" SHAPE_API const char* GetShapeName() { return "Ellipse"; }
编译生成ellipse.dll,将其放到app.exe所在目录。
3. 修改app.exe的工厂逻辑(仅需编译一次,后续加DLL无需改动)
在现有工厂类中添加动态加载DLL的逻辑,实现遍历目录、加载DLL、注册图形创建器三个步骤:
3.1 遍历目录获取所有DLL
#include <windows.h> #include <vector> #include <string> std::vector<std::wstring> GetAllDllsInCurrentDir() { std::vector<std::wstring> dllPaths; WIN32_FIND_DATAW findData; HANDLE hFind = FindFirstFileW(L"*.dll", &findData); if (hFind == INVALID_HANDLE_VALUE) return dllPaths; do { if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { dllPaths.push_back(findData.cFileName); } } while (FindNextFileW(hFind, &findData)); FindClose(hFind); return dllPaths; }
3.2 扩展工厂类加载外部图形
#include "ShapeInterface.h" #include <unordered_map> #include <functional> class ShapeFactory { private: std::unordered_map<std::string, std::function<IShape*(double, double)>> _creators; std::vector<HMODULE> _loadedDlls; // 保存DLL句柄,退出时释放 public: ShapeFactory() { // 注册内置图形(矩形、圆形) RegisterBuiltinShapes(); // 加载外部DLL中的图形 LoadExternalShapes(); } ~ShapeFactory() { // 释放所有加载的DLL for (HMODULE hDll : _loadedDlls) { FreeLibrary(hDll); } } IShape* CreateShape(const std::string& shapeName, double param1, double param2) { auto it = _creators.find(shapeName); return it != _creators.end() ? it->second(param1, param2) : nullptr; } private: void RegisterBuiltinShapes() { // 示例:注册矩形(宽、高) _creators["Rectangle"] = [](double w, double h) { return new Rectangle(w, h); }; // 示例:注册圆形(半径,第二个参数无用) _creators["Circle"] = [](double r, double) { return new Circle(r); }; } void LoadExternalShapes() { auto dlls = GetAllDllsInCurrentDir(); for (const auto& dllName : dlls) { HMODULE hDll = LoadLibraryW(dllName.c_str()); if (!hDll) continue; // 获取导出的函数指针 using GetShapeNameFunc = const char* (*)(); using CreateShapeFunc = IShape* (*)(double, double); GetShapeNameFunc getShapeName = reinterpret_cast<GetShapeNameFunc>( GetProcAddress(hDll, "GetShapeName") ); CreateShapeFunc createShape = reinterpret_cast<CreateShapeFunc>( GetProcAddress(hDll, "CreateShape") ); if (getShapeName && createShape) { std::string name = getShapeName(); _creators[name] = createShape; _loadedDlls.push_back(hDll); // 保存句柄防止被提前释放 } else { FreeLibrary(hDll); // 不是目标DLL,释放资源 } } } };
3.3 使用注意
创建图形实例后,调用shape->destroy()而非直接delete,避免跨模块内存释放错误:
// 示例使用 ShapeFactory factory; IShape* ellipse = factory.CreateShape("Ellipse", 5.0, 3.0); if (ellipse) { printf("Ellipse Area: %.2f\n", ellipse->getArea()); printf("Ellipse Perimeter: %.2f\n", ellipse->getPerimeter()); ellipse->destroy(); // 由DLL内部销毁对象 }
关键注意事项
- 导出库依赖:
ellipse.dll必须链接app.exe生成的app.lib,否则无法解析IShape的导出符号。 - 名字修饰:导出函数必须用
extern "C",确保GetProcAddress能通过函数名找到对应符号。 - 内存管理:统一使用接口的
destroy函数销毁对象,避免跨模块堆内存操作导致的崩溃。 - DLL放置:将
ellipse.dll放到app.exe所在目录,主程序才能遍历加载。
内容的提问来源于stack exchange,提问作者Queensalats
相关产品推荐
相关产品推荐

