Подтвердить что ты не робот

Использование shared_ptr в dll-интерфейсах

У меня есть абстрактный класс в моей 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 были скомпилированы с разными компиляторами (или разными настройками).

4b9b3361

Ответ 1

Ответ на ваш первый вопрос: Вызывается виртуальный деструктор в вашей 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() или обращать внимание на исключения, которые в обход неожиданно обходят поток управления.

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

  • Используя shared_ptr, убедитесь, что в DLL будет вызываться функция выделения ресурсов.
  • Посмотрите на ответы на этот вопрос.

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

Ответ 4

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

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

IBase *p = CreateInterface();
delete p;

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