Исходная задача — выделить из монолита (APP, проект на C++, VS2019) одну из подсистем и оформить её в виде DLL.
То есть, часть С++ классов переместится в отдельную MODULE.DLL.
APP получает С++ объекты из MODULE.DLL и может их dynamic_cast-ить, для получения нужных абстрактных интерфейсов (INTERFACE1,INTERFACE2).
Возник вопрос — нужно ли эти INTERFACE1, INTERFACE2 экспортировать из какой-то третьей DLL? Ну типа Core.DLL.
Или не нужно?
-----
Накатал тест (VS2019) и обнаружил, что эти интерфейсы экспортировать не надо.
То есть просто объявляем их как обычно, без dllimport/dllexport:
class INTERFACE1 {../*pure virtual methods*/..};
class INTERFACE2 {../*pure virtual methods*/..};
и без проблем — dynamic_cast на уровне APP, нормально к ним кастит.
Что удивительно.
Более того, если в MODULE.DLL определить внутренний абстрактный интерфейс (DUMMY_INTERFACE), который не светится в публичных хедерах, а в APP продублировать определение этого DUMMY_INTERFACE — то dynamic_cast<DUMMY_INTERFACE*> на уровне APP тоже отработает без проблем. Понятно, если у дубля будет иная бинарная структура чем у оригинала, то придет северное жывотное.
То есть, dynamic_cast юзает имя класса, без привязки к бинарнику.
Возникает вопрос - стоит ли полагаться на эту фичу?
Или таки надежнее завести CORE.DLL, которая будет эти интерфейсы экспортировать?
Я склоняюсь ко второму варианту — экспортировать. Все таки, это же модули "на базе C++", а не COM
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Возник вопрос — нужно ли эти INTERFACE1, INTERFACE2 экспортировать из какой-то третьей DLL? Ну типа Core.DLL.
КД>Или не нужно?
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Исходная задача — выделить из монолита (APP, проект на C++, VS2019) одну из подсистем и оформить её в виде DLL. КД>Я склоняюсь ко второму варианту — экспортировать. Все таки, это же модули "на базе C++", а не COM
Не надо экспортировать интерфейсы. Достаточно только объявить интерфейсы в заголовочных файлах.
Лучше сделать как в COM что бы класс умел себя удалять сам (дабы освободиться от конкретного runtime)
Далее проблемы могут возникать с параметрами которые из другого runtime если не по ссылкам передавать, можно огрести даже с тем же std::string или с векторами и т.п. КД>Опять же, мало ли — вдруг потом "фичу" сломают?
Не сломают, иначе потеряется обратная совместимость.
КД>[cut=Примеры реальных абстрактных интерфейсов]
... КД>Если определить эти интейфейсы с dllexport, то в секции экспорта CORE.DLL появляются методы: КД>По-ходу, это всякие встроенные конструкторы/деструкторы/операторы.
То что у вас экспортировалось это просто конструкторы и операторы присвоения по умолчанию (они нафиг никому не нужны)
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Исходная задача — выделить из монолита (APP, проект на C++, VS2019) одну из подсистем и оформить её в виде DLL.
Используй для этого beautiful capi — https://github.com/PetrPPetrov/beautiful-capi
Ключевые особенности:
— Распилка всех классов на С функции, потом сборка этих С функций в класс заново в header only style клиенте.
Как результат клиент может использовать как С, так и С++ API
— Нет QueryInterface, как в COM-подобных системах, но зато есть down_cast<>() — это фактически dynamic_cast,
однако обработка на стороне библиотеки, чтобы не было проблем с бинарной несовместимостью RTTI.
Из-за этого библиотека может быть на MSVC 2019, а клиент на Borland C++ compiler или Digital Mars C++ compiler.
— Вся аллокация\деаллокация идет на стороне библиотеки, чтобы не было проблем с разными менеджерами хипов
— Экспортируемые С функции имеют простые, понятные имена. Вместо знаков кракозябров (@!? и прочих),
используется только знак подчеркивания (_). Правило такое: сначала идет имя неймспейса (переведенное в snake_case),
затем добавляется имя класса (переведенное в snake_case), и имя метода в этом же стиле.
Например, метод Show класса Printer неймспейса Hello будет иметь имя hello_printer_show.
— Решается проблема с исключениями
Здравствуйте, GhostCoders, Вы писали:
GC>>Используй для этого beautiful capi — https://github.com/PetrPPetrov/beautiful-capi
GC>Ключевые особенности: GC>- Распилка всех классов на С функции, потом сборка этих С функций в класс заново в header only style клиенте. GC> Как результат клиент может использовать как С, так и С++ API GC>- Нет QueryInterface, как в COM-подобных системах, но зато есть down_cast<>() — это фактически dynamic_cast, GC> однако обработка на стороне библиотеки, чтобы не было проблем с бинарной несовместимостью RTTI. GC> Из-за этого библиотека может быть на MSVC 2019, а клиент на Borland C++ compiler или Digital Mars C++ compiler. GC>- Вся аллокация\деаллокация идет на стороне библиотеки, чтобы не было проблем с разными менеджерами хипов GC>- Экспортируемые С функции имеют простые, понятные имена. Вместо знаков кракозябров (@!? и прочих), GC> используется только знак подчеркивания (_). Правило такое: сначала идет имя неймспейса (переведенное в snake_case), GC> затем добавляется имя класса (переведенное в snake_case), и имя метода в этом же стиле. GC> Например, метод Show класса Printer неймспейса Hello будет иметь имя hello_printer_show. GC>- Решается проблема с исключениями
Еще бы файлы описания были бы в человеческом формате, а не только в xml.
Здравствуйте, kov_serg, Вы писали:
_>Еще бы файлы описания были бы в человеческом формате, а не только в xml.
Если честно не вижу проблемы с XML. Его проще парсить. В студии можно добавить .xsd схему для него (https://github.com/PetrPPetrov/beautiful-capi/blob/master/source/Capi.xsd)
и иметь auto-complete при наборе XML в студии.
Еще есть тикет — https://github.com/PetrPPetrov/beautiful-capi/issues/24
В двух словах: можно создать свой DSL для описания АПИ, парсить его при помощи, скажем LL(1) грамматик (или чем-то похожим),
а затем перегонять в структуру, которая сейчас получается после загрузки XML. То есть остальной код bcapi останется без изменений.
Однако у меня руки до этого не доходят.
Есть еще более интересная задумка — https://github.com/PetrPPetrov/beautiful-capi/issues/74
При помощи clang AST автоматически генерировать XML с описанием АПИ. Есть уже отдельный репозиторий с первыми шагами, но нет времени продолжать это.
Здравствуйте, kov_serg, Вы писали: КД>>Исходная задача — выделить из монолита (APP, проект на C++, VS2019) одну из подсистем и оформить её в виде DLL. КД>>Я склоняюсь ко второму варианту — экспортировать. Все таки, это же модули "на базе C++", а не COM _>Не надо экспортировать интерфейсы. Достаточно только объявить интерфейсы в заголовочных файлах. _>Лучше сделать как в COM что бы класс умел себя удалять сам (дабы освободиться от конкретного runtime)
Так и сделано для большей части классов. См. t_smart_interface — это упрощенный IUnknown. _>Далее проблемы могут возникать с параметрами которые из другого runtime если не по ссылкам передавать, можно огрести даже с тем же std::string или с векторами и т.п.
Рантайм у меня один и тот же. Я сам это дело буду контролировать. КД>>Опять же, мало ли — вдруг потом "фичу" сломают? _>Не сломают, иначе потеряется обратная совместимость.
Если вдруг они решат, что надо сломать — сломают КД>>Если определить эти интейфейсы с dllexport, то в секции экспорта CORE.DLL появляются методы: КД>>По-ходу, это всякие встроенные конструкторы/деструкторы/операторы. _>То что у вас экспортировалось это просто конструкторы и операторы присвоения по умолчанию (они нафиг никому не нужны)
Меня тут посетила мысль.
Нужно явно запретить часть этих методов у одного из (экспортируемых) классов — t_smart_interface. От него наследуется еще один (тоже экспортируемый) класс — t_err_record.
Здравствуйте, GhostCoders, Вы писали:
GC>Здравствуйте, GhostCoders, Вы писали:
GC>>Используй для этого beautiful capi — https://github.com/PetrPPetrov/beautiful-capi
GC>Ключевые особенности: GC>- Распилка всех классов на С функции, потом сборка этих С функций в класс заново в header only style клиенте. GC> Как результат клиент может использовать как С, так и С++ API GC>- Нет QueryInterface, как в COM-подобных системах, но зато есть down_cast<>() — это фактически dynamic_cast, GC> однако обработка на стороне библиотеки, чтобы не было проблем с бинарной несовместимостью RTTI. GC> Из-за этого библиотека может быть на MSVC 2019, а клиент на Borland C++ compiler или Digital Mars C++ compiler. GC>- Вся аллокация\деаллокация идет на стороне библиотеки, чтобы не было проблем с разными менеджерами хипов GC>- Экспортируемые С функции имеют простые, понятные имена. Вместо знаков кракозябров (@!? и прочих), GC> используется только знак подчеркивания (_). Правило такое: сначала идет имя неймспейса (переведенное в snake_case), GC> затем добавляется имя класса (переведенное в snake_case), и имя метода в этом же стиле. GC> Например, метод Show класса Printer неймспейса Hello будет иметь имя hello_printer_show. GC>- Решается проблема с исключениями
Более того, далеко не все языки умеют импортить сырые плюсовые интерфейсы, даже без учёта приколов с манглингом имён. Плоский сишный интерфейс в этом плане куда более интегрируемый.
Здравствуйте, Mr.Delphist, Вы писали:
MD>Более того, далеко не все языки умеют импортить сырые плюсовые интерфейсы, даже без учёта приколов с манглингом имён. Плоский сишный интерфейс в этом плане куда более интегрируемый.
Для меня это не актуально ... от слова совсем
Монолит представляет собой COM-сервер.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Потому что это внутренности. И мне не надо эти внутренности делать по правилам COM-интерфейсов.
Ясно. Тогда вопросов нет.
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Или таки надежнее завести CORE.DLL, которая будет эти интерфейсы экспортировать?
Чистые абстрактные классы экспортировать не только не нужно, но и бессмысленно — они же никак не участвуют в связывании. То, что помещается в таблицы при добавлении dllexport к их определениям — это, как тут уже отметили, чисто служебный код, генерируемый компилятором. В других модулях, где эти интерфейсы будут определены, компилятор сгенерит точно такой же код, а без определения их невозможно будет использовать.
По сути, абстрактный класс описывает только сигнатуры виртуальных функций и размер/порядок vtable, а все это не может экспортироваться через структуры PE.
Здравствуйте, Евгений Музыченко, Вы писали:
КД>>Или таки надежнее завести CORE.DLL, которая будет эти интерфейсы экспортировать?
ЕМ>Чистые абстрактные классы экспортировать не только не нужно, но и бессмысленно — они же никак не участвуют в связывании.
В конечном итоге, я тоже пришел к такому выводу.
Меня немного напрягало предупреждение C4275, но я решил на него забить.
Все нормально работает и без экспорта.
"CORE.DLL" все равно завел — поместил туда некоторые служебные обычные классы и абстрактные классы с виртуальными деструкторами.
ЕМ>То, что помещается в таблицы при добавлении dllexport к их определениям — это, как тут уже отметили, чисто служебный код, генерируемый компилятором.
Я сократил до одного экспортируемого метода на каждый экспортируемый интерфейс.
Полагаю, это конструктор по умолчанию, изничтожить который не получилось.
ЕМ>В других модулях, где эти интерфейсы будут определены, компилятор сгенерит точно такой же код, а без определения их невозможно будет использовать.
Дело в том, что меня пугал возможный конфликт реализаций этих самых служебных методов.
Типа APP компилируется с двумя (статическими) либами, которые в свою очередь юзают интерфейс. Вдруг реализации сгенерированных методов интерфейса при компиляции APP будут конфликтовать?
Опыта с такими вещами нет, поэтому голова придумывает потенциальные засады
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Коваленко Дмитрий, Вы писали: КД>Я сократил до одного экспортируемого метода на каждый экспортируемый интерфейс.
А смысл? КД>Дело в том, что меня пугал возможный конфликт реализаций этих самых служебных методов. КД>Типа APP компилируется с двумя (статическими) либами, которые в свою очередь юзают интерфейс. Вдруг реализации сгенерированных методов интерфейса при компиляции APP будут конфликтовать?
Конфликтовать может то, что участвует в связывании компоновщиком (link). В данном случае это только члены производных классов. Это легко проверить, сделав LIB или DLL, где в виртуальные функции интерфейса объявлены в одном порядке, и связав с нею модуль, в котором функции объявлены в другом порядке. Если связывание идет по именам функций, экспортируемых из библиотеки (например, в модуле явно создается объект класса, методы которого экспортируются библиотекой, и объявлены в модуле), то компилятор положит в vtable правильные адреса, взяв их из внешних ссылок. Если же библиотека не экспортирует имен и/или модуль их не импортирует (например, адрес объекта класса, реализованного библиотекой, добывается косвенным образом), то компилятор будет класть в vtable адреса в том порядке, в котором они объявлены в модуле.
Пример
Файл if1.h:
__interface Interface {
virtual int First (void) const abstract;
virtual int Second (void) const abstract;
virtual int Third (void) const abstract;
};
Interface const & GetIf ();
Файл if2.h:
__interface Interface {
virtual int Third (void) const abstract;
virtual int Second (void) const abstract;
virtual int First (void) const abstract;
};
Interface const & GetIf ();
Файл lib.cpp:
#include "if1.h"
class C1 : public Interface {
public:
virtual int First (void) const;
virtual int Second (void) const;
virtual int Third (void) const;