Есть libary. В ней класс. И методы класса, часть которых хочется экспортировать.
Есть DLL. Она включает library и экспортирует методы класса.
Как уже работает: def file с декорированными именами.
Проблема: меняя компилятор, платформу декорированные имена меняются. Получается для каждого случая надо иметь свой def файл.
Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций.
Есть способ обойти создание нескольких def файлов?
Сергей
Re: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Sergey_BG, Вы писали:
S_B>Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций.
в клиенте нужно делать __declspec(dllimport), а в библиотеке __declspec(dllexport)
Здравствуйте, Sergey_BG, Вы писали:
S_B>Здравствуйте.
S_B>Есть libary. В ней класс. И методы класса, часть которых хочется экспортировать. S_B>Есть DLL. Она включает library и экспортирует методы класса. S_B>Как уже работает: def file с декорированными именами. S_B>Проблема: меняя компилятор, платформу декорированные имена меняются. Получается для каждого случая надо иметь свой def файл. S_B>Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций. S_B>Есть способ обойти создание нескольких def файлов?
Ато. Способ называется extern "C"
Re[2]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Sergey_BG, Вы писали:
S_B>Здравствуйте.
S_B>Есть libary. В ней класс. И методы класса, часть которых хочется экспортировать. S_B>Есть DLL. Она включает library и экспортирует методы класса. S_B>Как уже работает: def file с декорированными именами. S_B>Проблема: меняя компилятор, платформу декорированные имена меняются. Получается для каждого случая надо иметь свой def файл. S_B>Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций. S_B>Есть способ обойти создание нескольких def файлов?
Собрать все функции, который хочется экспортировать в отдельный интерфейс.
Атрибуть __declspec(dllexport) поставить перед интерфейсом.
Экспортировать из библиотеки функцию — конструктор, которая вернет указатель на этот интерфейс.
Re[4]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Ну так-то да, но это не экспорт класса, а экспорт С-врапперов над классом.
BeautifulCapi сам генерирует такие врапперы https://github.com/PetrPPetrov/beautiful-capi
Здравствуйте, kov_serg, Вы писали:
_>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Здравствуйте, kov_serg, Вы писали:
_>>>Ато. Способ называется extern "C"
PD>>Методы класса ? _>Не шаблоны же _>
Здравствуйте, Chorkov, Вы писали:
C>Собрать все функции, который хочется экспортировать в отдельный интерфейс. C>Атрибуть __declspec(dllexport) поставить перед интерфейсом. C>Экспортировать из библиотеки функцию — конструктор, которая вернет указатель на этот интерфейс.
Интерфейс назвать IUnknown, а функцию — QueryInterface
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Chorkov, Вы писали:
C>Собрать все функции, который хочется экспортировать в отдельный интерфейс. C>Атрибуть __declspec(dllexport) поставить перед интерфейсом. C>Экспортировать из библиотеки функцию — конструктор, которая вернет указатель на этот интерфейс.
Есть только маленький нюанс. Некоторые люди передают std::string, std::vector, и други классы в качестве параметров.
И тут начинается е%%я маршалинг А потом еще всякие safe_guard-ы всплывают и ного разный корней торчащих из зарных runtime-ов
Кто-то тут на форуме авторитетно заявлял "мы не используем dll в C++ и вам не советуем"
Здравствуйте, Sergey_BG, Вы писали:
S_B>Есть способ обойти создание нескольких def файлов?
Напишу более подробно про beautiful-capi и как это должно работать.
beautiful-capi (для краткости bcapi) это скрипт на языке Python, который автоматически создает врапперы для задач, подобных вашей.
Вам нужна на выходе DLL-ка, которую можно было бы использовать с разными компиляторами С++ (Microsoft C++, Clang, GCC и другие).
Еше сама эта DLL в своей реализации использует некую (статическую, как понял) библиотеку, и экспортирует методы классов этой библиотеки через DEF файл.
Но из-за манглинга имена функций меняются в зависимости от платформы и комплиятора.
C bcapi делаем так:
1) Описываем нужный вам api в XML-ле с определенной схемой, вкратце API — это набор вложенных неймспейсов,
в каждом неймспесе может быть вложенный, а также описание класса. В классе есть методы. Еще есть stand-alone функции (в неймспесе).
Для каждого класса задается реализоционный класс (это тот в вашей статической библиотеки).
Причем вещи вроже std::vector, std::string и подобные оборачиваются. std::string можно обернуть через mapped_types, а можно и через полноценные обертки
(описать класс с нужными методами, а качестве реализационного задать std::string). Через mapped_types, это просто передавать std::string как const char*.
Есть, скажем метод SetName(const std::string& name), клиент вашей библиотеки видит метод, принимающий std::string, в клиентской обертке вызывается .str() для аргумента
(это преобразование задается через параметр mapped_types),
сам этот метод реализуется в DLL как extern "C" функция с именем имя_неймспейса_имя_класса_set_name(const char*), например, my_lib_my_class_set_name(const char*).
в обертке на стороне библиотеки создается новый экземпляр std::string при помощи конструктора, принимающего const char*.
Далее созданный std::string передается в реализационный класс, который ожидает std::string.
В том же Visual C++ размер sizeof(std::string) отличается, кажись, на 24 байта между Release и Debug версиями бинарей.
Поэтому для релизного клиента нельзя использовать дебажную DLL, и наоборот. Но это если использовать метод SetName(const std::string& name) напрямую.
Обернутую же bcapi библиотеку можно использовать с каким угодно клиентом.
Это я описал случай, если метод принимает строки как входной аргумент, но не модифицирует ее.
Если, скажем, строка возращается как значение, то тут нужна уже полноценная обертка над std::string.
Суть в чем: опиываете свою оберку над std::string с нужным функционалом, что-то в духе
<namespace name="MyCoolLibrary">
<namespace name="Std"> <!-- или std, STD, STL, stl - имя задаете свое -->
<class name="String" semantic="copy_semantic" implementation_class="std::string" implementation_header_name="string"> <!-- имя задаете свое -->
<constructor name="FromCStr"/>
<argument name="value" type="const char*"/>
</constructor>
<method name="GetLength" return="size_t" const="true" implementation_name="length"/>
<method name="GetStdString" return="std_string" const="true" implementation_name="c_str"/> <!-- возвращаемое значение - mapped_type - std_string -->
<!-- и другие необходымые методы, хотя можно обойтись двумя - GetStdString и SetStdString -->
</class>
<mapped_type name="std_string" c_type="const char*" implementation_type="std::string" c_2_impl="std::string({expression})" impl_2_c="{expression}.c_str()">
<cpp wrap_type="std::string" argument_wrap_type="const std::string&" c_2_wrap="std::string({expression})" wrap_2_c="{expression}.c_str()">
<include_header file="string" system="true"/>
</cpp>
<sharp wrap_type="string" c_2_wrap="{expression}" wrap_2_c="{expression}"/>
</mapped_type>
</namespace/>
<class name="MyClass" semantic="copy_semantic" implementation_class="имя_класса_из_вашей_library" implementation_header_name="хидер_файл_из_вашей_library"> <!-- имя задаете свое -->
<constructor name="FromCStr"...
<method name="GetName" return="MyCoolLibrary::Std::String" const="true"...
</class>
</namespace/>
Но это для случая, если возвращаемая строка, формируется динамически, например, то есть реализационный метод, например, такой:
std::string my_class::get_name() const
{
return std::string("Hello ") + std::string("World!");
}
2) Создаем проект DLL-ки к которой лиенкуем вашу статическую library, и включаем в состав этого проекта файл AutoGenWrap.cpp, сгенерированный bcapi.
3) AutoGenWrap.cpp содержит множество extern "C" функций с опцией __declspec(dllexport), то есть DEF файл не нужен. Для платформ Linux\mac используется другая директива (bcapi генериует код для 3-х платформ- Windows,Linux,Mac OSX — файл AutoGenWrap.cpp тот же самый, содержит внутри #ifdef _WIN32...).
4) Как результат на выходе имеем DLL-ку с интерфейсом на чистом С. Ну это если кто-то другой ее DependencyWalkером откроет, так и подумает. Но мы-то знаем, что внутри она реализована на С++.
5) Bcapi генериует набор header-only standalone файлов на С++, для C++ 03. В этом наборе файлов вновь определются классы,
такие как MyCoolLibrary::MyClass с необходимыми методами, конструкторами, операциями копирования. Эти обертки на клиентской стороне используют вот эти все extern "C" функциии, которые реализованы в нашей DLL-ке.
Сгенерированные хедеры пригодны для использования на трех платформах — Windows, Linux, Mac. Компилятор — любой, который держит С++ 03 (для даже С++ 98 тут сейчас сказать точно уже не смогу).
То есть библиотека DLL может быть собрана при помощи Visual C++ 2017, а клиентом может быть какой-нибудь Digital Mars C++ compiler, или старенький Borland C++ compiler, или Visual C++ 6.0.
Также внутри себя библиотека DLL может использовать последние фичи всех стандартов С++ 17, 14 — на которые будет способен компилятор используемый для компиляции этой DLL.
Клиентом может быть старый компилятор Visual C++ 6.0.
Также существует проблема несовместимых форматов .lib файлов (компаньоны для .dll в ОС Windows). Microsoft использует свой формат, Borland — свой.
Для этой проблемы есть решение.
Есть возможность не использовать .lib файлы, а загружать эти функции через LoadLibrary \ FreeLibrary \ GetProcAddress на Windows.
Это надо на клиенте спец. макрос включить. Для Linux\Mac используются функции dl_open, dl_close, dl_get_proc (пишу по памяти).
Здравствуйте, Sergey_BG, Вы писали:
S_B>Здравствуйте.
S_B>Есть libary. В ней класс. И методы класса, часть которых хочется экспортировать. S_B>Есть DLL. Она включает library и экспортирует методы класса. S_B>Как уже работает: def file с декорированными именами. S_B>Проблема: меняя компилятор, платформу декорированные имена меняются. Получается для каждого случая надо иметь свой def файл. S_B>Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций.
Сталкивался как-то с аналогичной ситуацией. Если память не изменяет, проблема решалась вызовом из исходника DLL функции-пустышки,
определенной в библиотеке. S_B>Есть способ обойти создание нескольких def файлов?
Перед __declspec(dllexport) добавить соглашение имен (по аналогии с WINAPI).
Re[2]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Vaynamond, Вы писали:
V>Перед __declspec(dllexport) добавить соглашение имен (по аналогии с WINAPI).
Нет способа задания "соглашения имен". Есть calling convention (__cdecl, __stdcall и прочие).
Они задают конвенцию вызова, а не имени, то есть определяют в каком порядке будут аргументы лежать в стеке,
кто чистит стек, как могут быть использованы регистры и т.д. Схема декорирования имен зависит от компилятора,
cl.exe использует одну схему, gcc использует другую — https://en.wikipedia.org/wiki/Name_mangling
Третий Рим должен пасть!
Re[3]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, GhostCoders, Вы писали:
GC>Здравствуйте, Vaynamond, Вы писали:
V>>Перед __declspec(dllexport) добавить соглашение имен (по аналогии с WINAPI). GC>Нет способа задания "соглашения имен". Есть calling convention (__cdecl, __stdcall и прочие). GC>Они задают конвенцию вызова, а не имени, то есть определяют в каком порядке будут аргументы лежать в стеке, GC>кто чистит стек, как могут быть использованы регистры и т.д. Схема декорирования имен зависит от компилятора, GC>cl.exe использует одну схему, gcc использует другую — https://en.wikipedia.org/wiki/Name_mangling
Пардон, промахнулся.
Re[4]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, Vaynamond, Вы писали:
V>Пардон, промахнулся.
Но я тоже промахнулся. Calling convention тоже вляет на naming convention.
Из той же статьи на вики:
int _cdecl f (int x) { return 0; }
int _stdcall g (int y) { return 0; }
int _fastcall h (int z) { return 0; }
на некоей платформе дает следующие имена:
_f
_g@4
@h@4
Однако, при помощи calling convention задать, или выбрать naming convention способа нет, это уже от копилятора зависит.
Третий Рим должен пасть!
Re[2]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, GhostCoders, Вы писали:
GC>Здравствуйте, Vaynamond, Вы писали:
GC>Однако, при помощи calling convention задать, или выбрать naming convention способа нет, это уже от копилятора зависит.
Вообщем, или делать обертки с использованием extern "C", или делать библиотеки под каждый компилятор.
Re[3]: Экспорт функций членов класса из другой библиотеки
Здравствуйте, AeroSun, Вы писали:
AS>Что-то как-то слишком переусложнённо. AS>Проще взять boost::dll и просто прикрутить свой deleter при создании объектов в dll
Переусложненно из-за того, что методы API нашей DLL работают с std::string.
Принимают std::string в качестве аргумента и возвращают его по значению (или по ссылке, или указателю, не важно).
Пример Boost.DLL использует std::string в качестве возвращаемого значения метода name():
Вот только это не работает в Boost.DLL. Если собрать DLL-ку при помощи Visual C++ 2015 в режиме Debug, а пример-EXE собрать
при помощи того-же компилятора, но в режиме Release — пример упадет.
Это потому что sizeof(std::string) в режиме Debug — 40 байт, а в режиме Release — 32.
Соответственно, нет бинарной совместимости, это даже при том, что используется одинаковой компилятор.
В Boost.DLL подумали над проблемой манглинга имен С++ и решили ее.
Но вот Boost.DLL никак, от слова совсем, не решает пробелему разного C++\С Runtime.
И решить эту проблему только через кастомные deleter не получится, так или иначе прийдёте к обертками над std::string и прочим вещам, которые есть в bcapi.
Ну или что-то в духе Microsoft COM\IFX COM\другого аналога кроссплатформенного COM использовать.