建立dll的正确方法

Right way to build dll

本文关键字:方法 dll 建立      更新时间:2023-10-16

我们有一个类a(如下所示)。

#ifdef DLL_ON_WORK
    #define DLL_SPEC __declspec(dllexport)
#else
    #define DLL_SEPC __declspec(dllimport)
class A
{
    friend B;
private:
    int a;
    int b;
    vector<int> c;
public:
    DLL_SPEC int Geta();
    DLL_SPEC int Getb();
    DLL_SPEC vector<int> Getc();
             int Calc();
private:
    void Seta(int val);
    void Setb(int val);
    void Setc(vector<int>& val);
};

我有几个问题

  1. 如果类A将在客户端代码中动态和静态地创建和删除,我必须将整个类A标记为DLL_SPEC吗
  2. 我需要为客户端代码创建"特殊"版本的头文件吗,比如SDK,其中的所有私有和不可用的客户端代码方法和字段都将被删除?我能做到吗
  3. 如果头文件将在客户端代码、完整类声明中使用,或者我只能指定客户端应该使用的类接口,那么它实际上应该提供什么

我知道,这些问题相互交叉,但我在构建库及其在客户端代码中的进一步使用方面有一些知识空白,如果你能推荐一些关于这个主题的书籍或文章,那就太好了。

在您的情况下,我会使用工厂模式(老实说,我总是使用工厂模式)。

在这种情况下,您将只导出两个功能

DLL_SPEC A *createClassA();
DLL_SPEC void destroyClassA(A *obj);

对于其余部分,您需要任何说明符virtual,这使得思考更容易处理。

优点:

  • 您可以将头文件分为两部分(工厂和通常的定义)。如果类的定义对于user和dll都是相同的
  • 每个方法前面都没有宏修饰符

缺点:

  • 只能通过factory函数创建和销毁对象。DLL内存边界将阻止在DLL中使用new和在主代码中删除。但这并不是一个真正的问题。如果使用std::shared_ptr,则可以提供特殊的销毁功能

您还可以将工厂函数作为static方法放入类中。在这种情况下,您可以声明构造函数和析构函数为private,这将阻止dll的用户在没有工厂的情况下创建A:

#ifdef DLL_ON_WORK
    #define DLL_SPEC __declspec(dllexport)
#else
    #define DLL_SEPC __declspec(dllimport)
class A
{
    friend B;
private:
    int a;
    int b;
    vector<int> c;
public:
    virtual int Geta();
    virtual int Getb();
    virtual vector<int> Getc();
    virtual int Calc();
    static DLL_SPEC A *createClassA();
    static DLL_SPEC void destroyClassA(A *obj);
private:
    virtual void Seta(int val);
    virtual void Setb(int val);
    virtual void Setc(vector<int>& val);
};

最后但并非最不重要。使用接口模式。只导出接口,而不导出类本身。将类的名称从"A"命名为"Aimplementation",这是从将导出的接口"IA"继承的。在您的情况下,界面将是这样的:

#ifdef DLL_ON_WORK
    #define DLL_SPEC __declspec(dllexport)
#else
    #define DLL_SEPC __declspec(dllimport)
class IA
{
public:
    virtual int Geta()=0; // with C++11 please use =nullptr instead of
    virtual int Getb()=0; 
    virtual vector<int> Getc()=0;
    virtual int Calc();

    static DLL_SPEC IA *createA();
    static DLL_SPEC void destroyA(IA *obj);
};

这是最巧妙的方式,也是你三个问题的答案。

更新

我之前忘记了virtual关键字,这对于这个解决方案是必要的。关键字virtual强制编译器生成一个vtable,该表包含使用new创建的每个对象的方法的入口点。

DLL中工厂方法的典型实现如下所示:

IA* IA::createA()
{
 return new Aimplemetation;
}
void IA::destroyA(IA *obj)
{
  delete static_cast<Aimplementation *>(obj);
}

要在DLL用户的代码中调用Geta,main看起来像:

void main()
{
 IA *a=IA::createIA();
 a->Geta();
 IA::destroyIA(a);
}

@Christophe在评论部分总结如下:

因此,诀窍在于,在客户端,编译器从header的类定义对象的vtable布局。然后他使用vtable间接寻址生成调用,而无需公开函数名称!当然也使用相同的调用约定。

通常每个库只导出两个函数。创建/初始化和销毁/关闭。在返回类的第一个实例后,我或多或少地公开了整个接口。在我看来,这也使代码更具可读性。这种模式也可以用于插件系统,其中客户端(例如Chrome)提供标头,插件DLL必须完成接口。