Экспорт функций членов класса из другой библиотеки
От: Sergey_BG Россия  
Дата: 19.07.19 09:57
Оценка:
Здравствуйте.

Есть libary. В ней класс. И методы класса, часть которых хочется экспортировать.
Есть DLL. Она включает library и экспортирует методы класса.
Как уже работает: def file с декорированными именами.
Проблема: меняя компилятор, платформу декорированные имена меняются. Получается для каждого случая надо иметь свой def файл.
Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций.
Есть способ обойти создание нескольких def файлов?
Сергей
Re: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 19.07.19 10:09
Оценка:
Здравствуйте, Sergey_BG, Вы писали:

S_B>Пробовал добавить __declspec(dllexport). Но так как данная функция определена в library, то она не экспортируется. Смотрел depedencyWalkerом, нет функций.


в клиенте нужно делать __declspec(dllimport), а в библиотеке __declspec(dllexport)

UPDATE: сорри, быстро читал, не прочел про library.
Советую использовать beautiful-capi — https://github.com/PetrPPetrov/beautiful-capi
Третий Рим должен пасть!
Отредактировано 19.07.2019 10:11 GhostCoders . Предыдущая версия .
Re: Экспорт функций членов класса из другой библиотеки
От: kov_serg Россия  
Дата: 19.07.19 10:19
Оценка:
Здравствуйте, 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]: Экспорт функций членов класса из другой библиотеки
От: Pavel Dvorkin Россия  
Дата: 19.07.19 10:28
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Ато. Способ называется extern "C"


Методы класса ?
With best regards
Pavel Dvorkin
Re[3]: Экспорт функций членов класса из другой библиотеки
От: kov_serg Россия  
Дата: 19.07.19 10:35
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, kov_serg, Вы писали:


_>>Ато. Способ называется extern "C"


PD>Методы класса ?

Не шаблоны же
struct A {
    virtual ~A() {}
    virtual int add(int x,int y) { return x+y; }
};

extern "C" {
  void* A_ctor() { return new A(); }
  void  A_dtor(void* p) { delete (A*)p; }
  int   A_add(void* handle,int x,int y) { return ((A*)handle)->add(x,y); }
};
Re: Экспорт функций членов класса из другой библиотеки
От: Chorkov Россия  
Дата: 19.07.19 10:48
Оценка:
Здравствуйте, 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 Россия  
Дата: 19.07.19 10:49
Оценка:
Здравствуйте, kov_serg, Вы писали:

<skipped>

Ну так-то да, но это не экспорт класса, а экспорт С-врапперов над классом.
With best regards
Pavel Dvorkin
Re[5]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 19.07.19 11:11
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Ну так-то да, но это не экспорт класса, а экспорт С-врапперов над классом.

BeautifulCapi сам генерирует такие врапперы
https://github.com/PetrPPetrov/beautiful-capi
Третий Рим должен пасть!
Отредактировано 19.07.2019 11:12 GhostCoders . Предыдущая версия .
Re[4]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 19.07.19 11:15
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Здравствуйте, kov_serg, Вы писали:


_>>>Ато. Способ называется extern "C"


PD>>Методы класса ?

_>Не шаблоны же
_>
_>struct A {
_>    virtual ~A() {}
_>    virtual int add(int x,int y) { return x+y; }
_>};

_>extern "C" {
_>  void* A_ctor() { return new A(); }
_>  void  A_dtor(void* p) { delete (A*)p; }
_>  int   A_add(void* handle,int x,int y) { return ((A*)handle)->add(x,y); }
_>};
_>


BeautifulCapi генерирует что-то очень похожее (https://github.com/PetrPPetrov/beautiful-capi):

HELLOWORLD_API void* HELLOWORLD_API_CONVENTION hello_world_printer_default()
{
    return new HelloWorld::PrinterImpl();
}
HELLOWORLD_API void HELLOWORLD_API_CONVENTION hello_world_printer_show_const(void* object_pointer)
{
    const HelloWorld::PrinterImpl* self = static_cast<HelloWorld::PrinterImpl*>(object_pointer);
    self->Show();
}
HELLOWORLD_API void* HELLOWORLD_API_CONVENTION hello_world_printer_copy(void* object_pointer)
{
    return new HelloWorld::PrinterImpl(*static_cast<HelloWorld::PrinterImpl*>(object_pointer));
}
HELLOWORLD_API void HELLOWORLD_API_CONVENTION hello_world_printer_delete(void* object_pointer)
{
    delete static_cast<HelloWorld::PrinterImpl*>(object_pointer);
}


где HELLOWORLD_API раскрывается в extern "C" __declspec(dllexport) (или в другие макросы, для Linux).
Третий Рим должен пасть!
Re[5]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 19.07.19 11:18
Оценка:
Здравствуйте, GhostCoders, Вы писали:

[skip]

для этого правда надо написать такой вот XML — https://github.com/PetrPPetrov/beautiful-capi/blob/master/examples/hello_world/library/hello_world.xml
<?xml version="1.0" encoding="utf-8" ?>
<hello_world:api xmlns:hello_world="http://gkmsoft.ru/beautifulcapi" project_name="HelloWorld">
  <namespace name="HelloWorld">
    <class name="Printer" lifecycle="copy_semantic" implementation_class_name="HelloWorld::PrinterImpl" implementation_class_header="PrinterImpl.h">
      <constructor name="Default"/>
      <method name="Show" const="true"/>
    </class>
  </namespace>
</hello_world:api>
Третий Рим должен пасть!
Re[2]: Экспорт функций членов класса из другой библиотеки
От: rg45 СССР  
Дата: 19.07.19 17:20
Оценка: +2 :))) :)
Здравствуйте, Chorkov, Вы писали:

C>Собрать все функции, который хочется экспортировать в отдельный интерфейс.

C>Атрибуть __declspec(dllexport) поставить перед интерфейсом.
C>Экспортировать из библиотеки функцию — конструктор, которая вернет указатель на этот интерфейс.

Интерфейс назвать IUnknown, а функцию — QueryInterface
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: Экспорт функций членов класса из другой библиотеки
От: kov_serg Россия  
Дата: 19.07.19 19:45
Оценка:
Здравствуйте, Chorkov, Вы писали:

C>Собрать все функции, который хочется экспортировать в отдельный интерфейс.

C>Атрибуть __declspec(dllexport) поставить перед интерфейсом.
C>Экспортировать из библиотеки функцию — конструктор, которая вернет указатель на этот интерфейс.
Есть только маленький нюанс. Некоторые люди передают std::string, std::vector, и други классы в качестве параметров.
И тут начинается е%%я маршалинг А потом еще всякие safe_guard-ы всплывают и ного разный корней торчащих из зарных runtime-ов
Кто-то тут на форуме авторитетно заявлял "мы не используем dll в C++ и вам не советуем"
Отредактировано 19.07.2019 19:49 kov_serg . Предыдущая версия .
Re: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 22.07.19 16:19
Оценка:
Здравствуйте, 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&amp;" 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 (пишу по памяти).


beautiful-capi — https://github.com/PetrPPetrov/beautiful-capi
Третий Рим должен пасть!
Re: Экспорт функций членов класса из другой библиотеки
От: Vaynamond Россия  
Дата: 26.07.19 07:26
Оценка:
Здравствуйте, 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]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 26.07.19 08:45
Оценка:
Здравствуйте, Vaynamond, Вы писали:

V>Перед __declspec(dllexport) добавить соглашение имен (по аналогии с WINAPI).

Нет способа задания "соглашения имен". Есть calling convention (__cdecl, __stdcall и прочие).
Они задают конвенцию вызова, а не имени, то есть определяют в каком порядке будут аргументы лежать в стеке,
кто чистит стек, как могут быть использованы регистры и т.д. Схема декорирования имен зависит от компилятора,
cl.exe использует одну схему, gcc использует другую — https://en.wikipedia.org/wiki/Name_mangling
Третий Рим должен пасть!
Re[3]: Экспорт функций членов класса из другой библиотеки
От: Vaynamond Россия  
Дата: 26.07.19 10:26
Оценка:
Здравствуйте, 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]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 26.07.19 10:49
Оценка:
Здравствуйте, 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]: Экспорт функций членов класса из другой библиотеки
От: AeroSun  
Дата: 26.07.19 11:12
Оценка:
Что-то как-то слишком переусложнённо.
Проще взять boost::dll и просто прикрутить свой deleter при создании объектов в dll
Re[5]: Экспорт функций членов класса из другой библиотеки
От: Vaynamond Россия  
Дата: 26.07.19 11:58
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Здравствуйте, Vaynamond, Вы писали:


GC>Однако, при помощи calling convention задать, или выбрать naming convention способа нет, это уже от копилятора зависит.

Вообщем, или делать обертки с использованием extern "C", или делать библиотеки под каждый компилятор.
Re[3]: Экспорт функций членов класса из другой библиотеки
От: GhostCoders Россия  
Дата: 26.07.19 14:02
Оценка:
Здравствуйте, AeroSun, Вы писали:

AS>Что-то как-то слишком переусложнённо.

AS>Проще взять boost::dll и просто прикрутить свой deleter при создании объектов в dll
Переусложненно из-за того, что методы API нашей DLL работают с std::string.
Принимают std::string в качестве аргумента и возвращают его по значению (или по ссылке, или указателю, не важно).

Пример Boost.DLL использует std::string в качестве возвращаемого значения метода name():
class BOOST_SYMBOL_VISIBLE my_plugin_api {
public:
   virtual std::string name() const = 0;
   virtual float calculate(float x, float y) = 0;

   virtual ~my_plugin_api() {}
};


Вот только это не работает в 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 использовать.
Третий Рим должен пасть!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.