我决定创建一个带有 OpenGL 渲染器的 DLL 库,以便以后可以在任何地方(嗯,几乎)使用它。也就是不依赖于编译器和CRT的版本,把.lib和.h文件连接到你的项目就足够了,不管版本和配置都可以使用。听说用通常的方式导出类是不行的,推荐使用C接口,只导出函数。但是由于我的库是由 OOP 构想的,我仍然想找到一种方法来以某种方式导出这些相同的类。结果,我遇到了所谓的“工厂模式”,即导出创建对象并返回指向它的指针的方法。问题似乎解决了,但出现了一些问题。在描述这种方法的文章中(它在这里),建议执行以下操作:
- 创建具有完全虚拟方法的基类(A 类)(以及清理内存的销毁方法)
- 从上述基类(A)创建一个后代类(B类),其中所有虚拟方法都将被覆盖
- 创建从DLL导出的对象创建方法,其中,在动态内存中(使用new)创建一个对象,但返回一个指向基类对象的指针(据我了解,指针是强制转换的)
结果,在我的代码中,它看起来像这样:
基类(接口):
#pragma once
#ifdef RENDERERGL_EXPORTS
#define RENDERERGL_API __declspec(dllexport)
#else
#define RENDERERGL_API __declspec(dllimport)
#endif
/**
* \brief Интерфейсный класс для рендерера
*/
class RendererGLInterface
{
public:
virtual void destroy() = 0;
virtual ~RendererGLInterface(){};
};
后代类(主类,.h 文件)
#pragma once
#include "RendererInterface.h"
/**
* \brief Основной класс рендерера
*/
class RendererGL : RendererGLInterface
{
private:
public:
/**
* \brief Очистка памяти (вызов деструктора)
*/
void destroy() override;
/**
* \brief Деструктор
*/
~RendererGL();
};
/**
* \brief Экспортируемая функция создания рендерера
* \return Указатель на объект рендерера
*/
extern "C" RENDERERGL_API RendererGLInterface* __cdecl CreateRenderer();
后代类,实现(.cpp 文件)
#include "Include/RendererGL.h"
void RendererGL::destroy()
{
delete this;
}
RendererGL::~RendererGL()
{
}
RendererGLInterface* CreateRenderer()
{
return reinterpret_cast<RendererGLInterface*>(new RendererGL());
}
一切似乎都很好,但是..
- 单独的接口类(带有虚拟方法)有什么意义。为什么不能返回不是基类型的指针,而是类本身类型的指针?
- 我正确理解需要销毁方法,因为必须从 DLL 中删除类对象?但是通过删除自己有多好
delete this?创建一个像“freeA”这样会删除A类对象的导出方法会更正确吗? - 也许目前已经有一些更方便的导出方式?来自 STL 的标准智能指针能否以某种方式让生活更轻松(如果可以,如何)?
提前致谢。
这种方法直接依赖于虚函数表,只有不同的编译器以相同的方式构建这样的表,我们才能谈论使用另一个编译器编译的可执行文件中已经编译好的动态库。当然,条件是在同一平台上,位深度等。
一般来说,使用抽象基类,称为接口,从它继承的类,称为实现。在这种情况下,实现只存在于 DLL 中,并且接口在调用程序中使用。工作顺序大致如下:
最终,由于实现类是从抽象继承而来的,所以会使用多态性,编译器会在通过基抽象类访问时从虚函数表中替换值。
接口不应包含除空方法以外的任何方法;将其设为结构而不是类更方便。任何需要隐藏的东西都可以隐藏在实现中。
您导出的函数返回一个指向该类的指针:
C 函数只能返回指向 struct 或 的指针
void *,不能返回类。通用选项:好吧,回答你的问题:
因为在链接时将没有实现的方法,并且您要在其中使用 DLL 中的对象的程序根本不会被组装。
是的。就是为了这个。
如果您希望您的对象自行清理,这没有什么可谴责的。
它只是有所不同,但实际上,它向 DLL 添加了一个函数。类中方法的存在使我们免于这种情况。此外,在 C 函数中,您必须传递一个空指针并在内部对其进行标识才能正确删除它,这与
destroy()对象知道关于自身的一切的方法不同。好吧,如果将来CreateRenderer它会创建不同的对象,那么通过他们的方法进行删除会更容易。总的来说,这远不是一种处理 DLL 的新方法;它长期以来一直被 Microsoft 用于COM 对象,甚至已经过时。可以将类本身作为一个整体和单独的方法导出,通常我会推荐使用。