构建DLL的正确方法

时间:2015-02-14 10:15:06

标签: c++ dll architecture

我们有一个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. 如果它将在客户端代码,完整类声明中使用,或者我只能指定客户端应该使用的类接口,那么应该提供什么实际头文件?
  4. 我知道,这些问题相互交叉,但我在构建库及其在客户端代码中的进一步使用方面存在一些差距,如果您可以就此主题推荐一些书籍或文章,那就很好。< / p>

1 个答案:

答案 0 :(得分:2)

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

在这种情况下,您只会导出两个函数

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

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

优点:

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

缺点:

  • 您只能通过工厂功能创建和销毁对象。 DLL内存bounderies将阻止在主代码中使用新的dll和删除。但这不是一个真正的问题。如果使用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&#39;到实现&#39; Aimplementation&#39;它继承自界面&#39; IA&#39;将被出口。界面将与您的情况类似:

#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,主要内容如下:

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

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

  

所以诀窍是在客户端,编译器从中推断出来   header的类定义了对象的vtable布局。然后他   使用vtable间接生成调用而不需要公开   功能名称!当然还使用相同的调用约定。

通常我只为每个库导出两个函数。一个create / init和destroy / close。在给出一个类的第一个实例后,我或多或少暴露了整个接口。这使得代码在我看来更具可读性。这种模式也可以用于插件系统,其中客户端(例如Chrome)提供头部并且插件-DLL必须满足接口。