Экспорт классов, содержащих std :: objects (vector, map и т. Д.) Из dll - PullRequest
58 голосов
/ 20 апреля 2009

Я пытаюсь экспортировать классы из DLL, которые содержат такие объекты, как std :: vectors и std :: strings - весь класс объявлен как dll export через:

    class DLL_EXPORT FontManager
{

Проблема в том, что для членов сложных типов я получаю это предупреждение:

предупреждение C4251: «FontManager :: m__fonts»: класс «std :: map <_Kty, _Ty>» должен иметь dll-интерфейс для использования клиентами класса «FontManager» с [ _Kty = станд :: строка, _Ty = tFontInfoRef ]

Я могу удалить некоторые предупреждения, поместив перед ними следующее предварительное объявление класса, хотя я не изменяю тип самих переменных-членов:

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;
template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator<tCharGlyphProviderRef> >;
std::vector<tCharGlyphProviderRef> m_glyphProviders;

Похоже, что предварительное объявление «внедряет» DLL_EXPORT, когда член компилируется, но безопасно ли это? Действительно ли это что-то меняет, когда клиент компилирует этот заголовок и использует контейнер std на своей стороне? Будет ли в будущем использоваться такой контейнер DLL_EXPORT (и, возможно, не встроенный?)? И действительно ли это решает проблему, о которой пытается предупредить предупреждение?

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

Я использую Visual Studio 2003 со стандартной библиотекой STD.

---- Обновление ----

Я хотел бы нацелиться на вас больше, хотя, как я вижу, ответы являются общими, и здесь мы говорим о контейнерах и типах std (таких как std :: string) - возможно, вопрос на самом деле:

Можем ли мы отключить предупреждение для стандартных контейнеров и типов, доступных как клиенту, так и dll, через одни и те же заголовки библиотеки и обращаться с ними так же, как мы обращаемся с int или любым другим встроенным типом? (Мне кажется, это работает правильно на моей стороне.) Если да, то какие должны быть условия, при которых мы можем это сделать?

Или, может быть, использование таких контейнеров должно быть запрещено или, по крайней мере, быть крайне осторожным, чтобы убедиться, что никакие операторы присваивания, конструкторы копирования и т. Д. Не будут встроены в клиент DLL?

В общем, я хотел бы знать, считаете ли вы хорошей идеей проектирование интерфейса dll с такими объектами (и, например, использование их для возврата содержимого клиенту в качестве типов возвращаемого значения) и почему - я бы хотел бы иметь высокоуровневый интерфейс к этой функциональности ... может быть, лучшее решение - это то, что предложил Нил Баттерворт - создание статической библиотеки?

Ответы [ 12 ]

52 голосов
/ 20 апреля 2009

Когда вы прикасаетесь к члену вашего класса с клиента, вам необходимо предоставить DLL-интерфейс. DLL-интерфейс означает, что компилятор создает функцию в самой DLL и делает ее импортируемой.

Поскольку компилятор не знает, какие методы используются клиентами класса DLL_EXPORTED, он должен обеспечить, чтобы все методы были экспортированы dll. Он должен обеспечить, чтобы все члены, к которым могут обращаться клиенты, тоже должны экспортировать свои функции dll. Это происходит, когда компилятор предупреждает вас о методах, которые не экспортируются, и компоновщик клиента отправляет ошибки.

Не каждый участник должен быть отмечен с помощью dll-export, например частные участники не доступны для клиентов. Здесь вы можете игнорировать / отключать предупреждения (остерегайтесь сгенерированных компилятором dtor / ctors).

В противном случае члены должны экспортировать свои методы. Форвардное объявление их с помощью DLL_EXPORT не экспортирует методы этих классов. Вы должны пометить соответствующие классы в их модуле компиляции как DLL_EXPORT.

Что сводится к ... (для не экспортируемых dll членов)

  1. Если у вас есть участники, которые не / не могут использоваться клиентами, отключите предупреждение.

  2. Если у вас есть члены, которые должны использоваться клиентами, создайте оболочку dll-export или создайте методы косвенного обращения.

  3. Чтобы сократить количество видимых снаружи элементов, используйте такие подходы, как PIMPL идиома .


template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

Это создает экземпляр специализации шаблона в текущем модуле компиляции. Так что это создает методы std :: allocator в dll и экспортирует соответствующие методы. Это не работает для конкретных классов, так как это только создание экземпляров шаблонных классов.

17 голосов
/ 01 мая 2009

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

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

Итак, не выставляйте контейнеры напрямую через границы DLL. Если вам нужно представить элементы контейнера, сделайте это с помощью методов доступа. В указанном случае отделите интерфейс от реализации и предоставьте интерфейс на уровне DLL. Использование контейнеров std - это деталь реализации, к которой клиент вашей DLL не должен иметь доступ.

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

7 голосов
/ 05 августа 2010

Есть и другие проблемы.

Некоторые контейнеры STL «безопасны» для экспорта (например, векторные), а некоторые - нет (например, карта).

Карта, например, небезопасна, потому что она (в любом случае в распределении MS STL) содержит статический член с именем _Nil, значение которого сравнивается в итерации для проверки конца. Каждый модуль, скомпилированный с помощью STL, имеет свое значение для _Nil, поэтому карта, созданная в одном модуле, не будет повторяться из другого модуля (она никогда не обнаруживает конец и не взрывается).

Это применимо, даже если вы статически ссылаетесь на библиотеку, так как вы никогда не можете гарантировать, что будет значение _Nil (оно не инициализировано).

Я считаю, что STLPort не делает этого.

6 голосов
/ 06 марта 2011

Лучший способ справиться с этим сценарием:

создайте свою библиотеку, назвав ее компилятором и версиями stl, включенными в имя библиотеки, точно так же, как библиотеки boost.

Примеры:

- FontManager-msvc10-mt.dll для версии dll, специфичной для компилятора MSVC10, со значением по умолчанию stl.

- FontManager-msvc10_stlport-mt.dll для версии dll, специфичной для компилятора MSVC10, с портом stl.

- FontManager-msvc9-mt.dll для версии dll, специфичной для компилятора MSVC 2008, со значением по умолчанию stl

- libFontManager-msvc10-mt.lib для статической версии lib, специфичной для компилятора MSVC10, со значением по умолчанию stl.

следуя этому шаблону, вы избежите проблем, связанных с различными реализациями stl. помните, реализация stl в vc2008 отличается от реализации stl в vc2010.

Смотрите ваш пример, используя библиотеку boost :: config:

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#  pragma warning( push )
#  pragma warning( disable: 4251 )
#endif

class DLL_EXPORT FontManager
{
public:
   std::map<int, std::string> int2string_map;
}

#ifdef BOOST_MSVC
#  pragma warning( pop )
#endif
5 голосов
/ 20 апреля 2009

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

4 голосов
/ 04 августа 2009

Найдено этой статьи . Короче говоря, у Аарона есть «реальный» ответ выше; Не выставляйте стандартные контейнеры через границы библиотеки.

2 голосов
/ 08 апреля 2015

Экспорт классов, содержащих std :: objects (vector, map и т. Д.) Из dll

См. Также статью Microsoft KB 168958 Как экспортировать создание экземпляра класса Standard Template Library (STL) и класса, который содержит член данных, являющийся объектом STL . Из статьи:

Для экспорта класса STL

  1. И в DLL-файле, и в EXE-файле свяжите с одной и той же DLL-версией среды выполнения C. Либо свяжите оба с Msvcrt.lib (сборка релиза), либо связать оба с Msvcrtd.lib (отладочная сборка).
  2. В DLL укажите спецификатор __declspec в объявлении экземпляра шаблона, чтобы экспортировать экземпляр класса STL из DLL.
  3. В файле .exe укажите спецификаторы extern и __declspec в объявлении создания экземпляра шаблона, чтобы импортировать класс из DLL. Это приводит к предупреждению C4231 "нестандартное расширение используется: 'extern' перед явным созданием шаблона. "Вы можете игнорировать это предупреждение.

И

Экспорт класса, содержащего элемент данных, который является объектом STL

  1. И в DLL-файле, и в EXE-файле свяжите с одной и той же версией DLL времени выполнения C. Либо свяжите оба с Msvcrt.lib (сборка релиза), либо связать оба с Msvcrtd.lib (отладочная сборка).
  2. В DLL укажите спецификатор __declspec в объявлении экземпляра шаблона, чтобы экспортировать экземпляр класса STL из DLL.

    ПРИМЕЧАНИЕ. Вы не можете пропустить шаг 2. Вы должны экспортировать создание экземпляра класса STL, который вы используете для создания элемента данных.
  3. В DLL укажите спецификатор __declspec в объявлении класса для экспорта класса из DLL.
  4. В файле .exe предоставьте спецификатор __declspec в объявлении класса для импорта класса из DLL. Если класс, который вы экспортируете, имеет один или несколько базовых классов, тогда вы должны экспортируйте также базовые классы.

    Если экспортируемый класс содержит элементы данных, которые имеют тип класса, то вы должны экспортировать классы членов данных.
2 голосов
/ 21 ноября 2009

Хотя эта ветка довольно старая, я недавно обнаружил проблему, которая заставила меня снова подумать о наличии шаблонов в моих экспортируемых классах:

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

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

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

Спасибо за любое просвещение

1 голос
/ 20 апреля 2009

В таких случаях рассмотрите использование идиомы pimpl. Скрыть все сложные типы за одну пустоту *. Компилятор обычно не замечает, что ваши члены являются частными и все методы включены в DLL.

0 голосов
/ 07 октября 2015

Лучший подход для использования в таких сценариях - использовать шаблон проектирования PIMPL.

...