2015-02-14 5 views
2

У нас есть класс A (опущен ниже).Правильный способ построения dll

#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. Если класс А будет статически и динамически создавать и удалять в коде клиента, я должен отметить целый класс А как DLL_SPEC?
  2. Нужно создать «специальную» версию файла заголовка для кода клиента, что-то вроде SDK, в котором будут удалены все частные и непригодные для использования методы и поля клиентского кода? Я могу это сделать?
  3. Какой фактический заголовочный файл должен содержать, если он будет использоваться в клиентском коде, объявлении полного класса, или я могу указать только интерфейс класса, который должен использоваться клиентом?

Я знаю, что вопросы пересекаются между собой, но у меня есть некоторые пробелы в знаниях о создании библиотек и их дальнейшем использовании в клиентском коде, и если вы можете порекомендовать некоторые книги или статьи по этой теме, это будет хорошо.

+0

Вы уже прыгнули в акулу, обнажая std :: vector. В нем представлены детали реализации C++ (макет объекта, хранилище, исключения), который требует, чтобы клиентский программист использовал тот же самый компилятор, используя те же самые варианты сборки, используя ту же самую библиотеку CRT и C++. Так что это уже не имеет значения. –

ответ

2

Я бы использовал шаблон Factory в вашем случае (честно говоря, я всегда использую заводскую модель).

В этом случае, вы будете экспортировать только две функции

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

для отдыха, вам нужен любой спецификатор virtual, это делает думает гораздо проще в обращении.

Преимущество:

  • Вы можете разделить ваши файлы заголовков на две части (завод унд обычное определение). Если определение вашего класса идентично определению как для пользователя, так и для dll.
  • Никакой макромодификатор перед каждым способом.

Недостаток:

  • Вы можете только создавать и уничтожать объекты с помощью заводских функций. DLL Memory bounderies предотвратит использование новых в dll и удаление в вашем основном коде. Но это не проблема. Если вы используете std::shared_ptr, вы можете обеспечить специальную функцию уничтожения.

Вы также можете разместить свои заводские функции в своем классе как методы static. В этом случае вы можете объявить конструктор и деструктор частный, который предотвращает пользователю библиотеки DLL, чтобы создать без завода:

#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); 
}; 

который, ИМХО гладкий путь, а также ответ на ваши три вопроса.

UPDATE

Я забывая virtual ключевое слово перед тем, что необходимо для этого решения. Ключевое слово virtual вынуждает компилятор генерировать vtable с точками входа в методы с каждым объектом, созданным с помощью new.

Типичная реализация фабричных методов в DLL выглядит следующим образом:

IA* IA::createA() 
{ 
return new Aimplemetation; 
} 

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

Для вызова Geta в коде с пользователем DLL, основной будет выглядеть так:

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

@Christophe суммировал его, как следует в разделе комментариев:

Итак, трюк в том, что на стороне клиента, компилятор выводит из определения класса заголовка компоновку vtable объекта. Затем он генерирует вызов, используя ссылку vtable, без необходимости выставлять имя функции! и, конечно же, используя те же соглашения о вызовах.

Обычно я экспортирую только две функции в библиотеку. Создайте/init и уничтожьте/закройте. С первым экземпляром класса, данным обратно, я показываю более или менее весь интерфейс. Это делает код более читаемым, на мой взгляд. Этот тип шаблонов можно также использовать для плагиновых систем, где клиент (например, Chrome) предоставляет заголовок, а плагин-DLL должен выполнять интерфейсы.

+0

Интересно. Но я не понял, как клиент может называть Geta() на объекте, полученном с завода? – Christophe

+0

@ Christophe У меня был небольшой недостаток в моем образце. Обновление покажет, как это работает –

+0

ОК, я вижу. Таким образом, трюк заключается в том, что на стороне клиента компилятор выводит из определения класса заголовка компоновку vtable объекта. Затем он генерирует вызов, используя обратную связь vtable, без необходимости раскрывать имя функции! и, конечно же, используя те же соглашения о вызовах. Отлично ! – Christophe

Смежные вопросы