Использование shared_ptr в dll-интерфейсах - PullRequest
21 голосов
/ 22 октября 2009

В моей dll есть абстрактный класс.

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

Я хочу получить IBase в моем exe-файле, который загружает dll. Первый способ - создать следующую функцию

IBase * CreateInterface();

и добавить виртуальную функцию Release() в IBase.

Второй способ - создать другую функцию

boost::shared_ptr<IBase> CreateInterface();

и функция Release() не требуется.

Вопросы.

1) Верно ли, что освобождение деструктора и памяти вызывается в dll (не в exe-файле) во втором случае ?

2) Хорошо ли работает второй случай , если exe-файл и dll были скомпилированы с разными компиляторами (или с разными настройками).

Ответы [ 4 ]

18 голосов
/ 22 октября 2009

Ответ на ваш первый вопрос: Виртуальный деструктор в вашей dll называется - информация о его местонахождении встроена в ваш объект (в vtable). В случае освобождения памяти это зависит от того, насколько дисциплинированны пользователи вашего IBase. Если они знают, что должны вызвать Release() и считают, что исключение может обойти поток управления в неожиданном направлении, будет использоваться правильный.

Но если CreateInterface() возвращает shared_ptr<IBase>, это может связать правильную функцию освобождения прямо с этим умным указателем. Ваша библиотека может выглядеть так:

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

Таким образом, каждый пользователь вашей DLL легко защищен от утечек ресурсов. Им никогда не придется беспокоиться о вызове Release() или обращать внимание на исключения, удивительно обходящие их поток управления.

Чтобы ответить на ваш второй вопрос: Недостатки этого подхода четко изложены другими ответом с: ваша аудитория должна использовать тот же компилятор, компоновщик, настройки библиотеки как ты. И если их может быть довольно много, это может стать серьезным недостатком для вашей библиотеки. Вы должны выбрать: Безопасность против большей аудитории

Но есть возможная лазейка: Используйте shared_ptr<IBase> в своем приложении, т.е.

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

Таким образом, никакой специфичный для реализации объект не передается через границу DLL. Тем не менее, ваш указатель надежно спрятан за shared_ptr, который звонит DestroyFromLibrary в нужное время, даже если func() выдает исключение или нет.

6 голосов
/ 22 октября 2009

Я бы не советовал использовать shared_ptr в интерфейсе. Даже использование C ++ вообще в интерфейсе DLL (в отличие от «extern C» только подпрограмм) проблематично, поскольку искажение имен не позволит вам использовать DLL с другим компилятором. Использование shared_ptr особенно проблематично, поскольку, как вы уже определили, нет гарантии, что клиент DLL будет использовать ту же реализацию shared_ptr, что и вызывающая сторона. (Это потому, что shared_ptr является классом шаблона, а реализация полностью содержится в заголовочном файле.)

Чтобы ответить на ваши конкретные вопросы:

  1. Я не совсем уверен, что вы спрашиваете здесь ... Я предполагаю, что ваша DLL будет содержать реализации классов, полученных из IBase. Код для их деструкторов (а также остальной код), в обоих ваших случаях, будет содержаться в DLL. Однако, если клиент инициирует уничтожение объекта (вызывая delete в первом случае или выпуская последний экземпляр shared_ptr из области видимости во втором случае), то деструктор будет называться из код клиента.

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

2 голосов
/ 22 октября 2009
  1. Использование shared_ptr обеспечит вызов функции освобождения ресурса в DLL.
  2. Посмотрите на ответы на этот вопрос .

Выходом из этой проблемы является создание чистого интерфейса C и тонкой полностью встроенной оболочки C ++.

1 голос
/ 22 октября 2009

По вашему первому вопросу: я делаю обоснованное предположение и не говорю из опыта, но мне кажется, что во втором случае освобождение памяти будет называться "в .exe". Когда вы вызываете delete object;, происходят две вещи: во-первых, вызываются деструкторы, а во-вторых, освобождается память для объекта. Первая часть, вызов деструктора, определенно будет работать, как вы ожидаете, вызывая правильные деструкторы в вашей dll. Однако, так как shared_ptr является шаблоном класса, его деструктор генерируется в вашем .exe, и поэтому он будет вызывать оператор delete () в вашем exe, а не тот, что в .dll. Если они были связаны с разными версиями времени выполнения (или даже статически связаны с одной и той же версией времени исполнения), это должно привести к ужасному неопределенному поведению (в этой части я не совсем уверен, но, похоже, логично так) , Есть простой способ проверить, является ли то, что я сказал, правдой - переопределить глобальный оператор delete в вашем exe, но не в вашей dll, поместите в него точку останова и посмотрите, что вызывается во втором случае сделай это сам, но у меня так много времени, чтобы расслабиться, к сожалению).

Обратите внимание, что та же самая ошибка существует для первого случая (вы, кажется, это понимаете, но на всякий случай). Если вы делаете это в exe:

IBase *p = CreateInterface();
delete p;

тогда вы находитесь в том же самом операторе вызова, новом в dll, и вызове оператора delete в exe. Вам понадобится либо соответствующая функция DeleteInterface (IBase * p) в вашей dll, либо метод Release () в IBase (который не должен быть виртуальным, просто не встроенным) для единственной цели вызова нужной памяти функция освобождения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...