Добавить в корзинуПозвонить
Найти в Дзене
Хроники Георга

Renga API. Погружение в разработку. Часть 6 - Приложения на C++ и особенности отладки

Предыстория: Наконец-то настал момент, когда мои эксперименты с C++ нашли практическую сторону и я займусь чем-то полезным. Задумка попробовать некоторые плюсовые библиотеки была давно, запаздывала только реализация - я честно скажу, садился и бросал C++ раза четыре, уж оооочень жестко было вникать сразу с хардкорной темы - сборка open-source проектов с кучей внешних библиотек и непонятной внутренней кухней приложения. Отдельные open-source проекты типа OpenCASCADE, GMSH, LibreDWG я даже успешно собирал и они работали, некоторые типа SFCGAL или CloudCompare вводили меня в ужас количеством библиотек и сложностью сборки. Тем не менее я не бросал и двигался дальше. В данной статье хотелось бы уделить внимание важной и сложной теме - отладке Renga под C++. Проблема имеет 2 особенности - программа Renga не поддерживает возможности загрузки и отладки плагинов как это реализовано в Revit/AutoCAD, а также сама идея писать на C++ влечет некоторые неприятные обходные пути таких действий, но - об
Оглавление

Предыстория: Наконец-то настал момент, когда мои эксперименты с C++ нашли практическую сторону и я займусь чем-то полезным. Задумка попробовать некоторые плюсовые библиотеки была давно, запаздывала только реализация - я честно скажу, садился и бросал C++ раза четыре, уж оооочень жестко было вникать сразу с хардкорной темы - сборка open-source проектов с кучей внешних библиотек и непонятной внутренней кухней приложения. Отдельные open-source проекты типа OpenCASCADE, GMSH, LibreDWG я даже успешно собирал и они работали, некоторые типа SFCGAL или CloudCompare вводили меня в ужас количеством библиотек и сложностью сборки. Тем не менее я не бросал и двигался дальше.

В данной статье хотелось бы уделить внимание важной и сложной теме - отладке Renga под C++. Проблема имеет 2 особенности - программа Renga не поддерживает возможности загрузки и отладки плагинов как это реализовано в Revit/AutoCAD, а также сама идея писать на C++ влечет некоторые неприятные обходные пути таких действий, но - обо всем по порядку.

1. Немного про Renga API

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

Немного разбавляет положение GitHub страница вендора с исходным кодом плагина ModelExplorer представленного среди упомянутых расширений выше (надеюсь в свете начавшихся массовых блокировок GitHub российских разработчиков его это не затронет, т.к. зеркал я не видел).

Другим живым примером можно назвать репозитории ModPlus популярного разработчика приложений для AutoCAD, Revit, Renga - Александра Пекшева. Для Renga там числятся 7 страничек, язык разработки - C# . Небольшая подборка из также на С # была и у меня в январской серии статей по интеграции Renga с Dynamo Core.

На этом наверное и всё. А теперь переходим к ягодках - к разработке плагинчика на C++.

Стоит отметить общую особенность разработки на любых языках программирования под Renga - это механику работы через COM, даже на С++, несмотря на то, исходное API у продукта на C++.

2. О начале разработки на C++

В отличие от .NET здесь есть свои "языковые" особенности, диктующие свои правила логики приложения (библиотеки).

Если в .NET у нас была библиотека классов (на .NET Framework), то в C++ используется похожий тип приложения - Dynamic Link Library. При всем при этом, несмотря на данный тип, для отладки приложения мы будем использовать сущность статичной библиотеки, но об этом далее.

Нужный тип библиотеки
Нужный тип библиотеки

Если в .NET мы подключали к проекту COM-библиотеку (описание функций) "RengaCOMAPI.tlb" и "Renga.NET.PluginUtility.dll"; то в случае с C++ вместо "Renga.NET.PluginUtility.dll" у нас появляется набор заголовочных файлов (header-файлов), идущих в поставке с Renga SDK - они описывают те же структуры, что и в .NET-библиотеке, то есть Enum и прочие доп. определения. Также как в .NET, здесь прямое подключение библиотеки RengaCOMAPI.tlb приведет скорее всего к появлению в проекте ошибок (не найден файл *.tlh).

Здесь есть интересный принцип работы в C++ с COM: а именно, если функции определены в *.tlb-библиотеке, то нам надо после ее импорта в программу (и не как include, а как import) единожды собрать проект, для того, чтобы в bin-директории у нас появился заголовочный файл для данного ЯП (в нашем случае, для C++ - это *.tlh файл).

Из-за специфики отладки приложения и в целом принципа работы с COM (расскажу далее), я буду следовать следующей архитектуре приложения: DLL-библиотека и консольное приложение в рамках одного решения (solution).

2.1 Создание структуры приложения

Сразу упомяну исходный код демонстрируемого примера https://github.com/GeorgGrebenyuk/renga_3d-export/tree/main/src. Если GitHub меня заблокирует, то я перемещусь на GitFlic и ссылки обновлю ...

1. Создаем новый проект для DLL-библиотеки
1. Создаем новый проект для DLL-библиотеки
2. Выбираем директорию размещения проекта, имя решения и имя проекта (нашей будущей библиотеки классов)
2. Выбираем директорию размещения проекта, имя решения и имя проекта (нашей будущей библиотеки классов)
3. Создаем новый проект
3. Создаем новый проект
4. Консольное приложение
4. Консольное приложение
5. В папке, где лежит файл решения
5. В папке, где лежит файл решения
6. Консольный проект выбираем как стартовый
6. Консольный проект выбираем как стартовый

Теперь переходим к настройке проектов - начнем с первого (нашей библиотеки lib_core):

2.2 Редактирование параметров основной библиотеки

8. Факт создания решения с проектами во вложенной папке позволяет нам собирать проекты в единой папке без необходимости донастройки относительных путей в конфигах проекта (но об этом позже)
8. Факт создания решения с проектами во вложенной папке позволяет нам собирать проекты в единой папке без необходимости донастройки относительных путей в конфигах проекта (но об этом позже)
9. Опциональный шаг - отключение заголовочных файлов на этапе пред-компиляции (на текущий момент это не нужно, лишние пару файлов болтающихся)
9. Опциональный шаг - отключение заголовочных файлов на этапе пред-компиляции (на текущий момент это не нужно, лишние пару файлов болтающихся)
10. Переход к добавлению ссылок на подключаемый каталог с заголовочными файлами
10. Переход к добавлению ссылок на подключаемый каталог с заголовочными файлами
11. Подключаем директории из Renga SDK
11. Подключаем директории из Renga SDK
12. По причине избавления от пред-компиляции заголовочных файлов, мы можем выбросить базовые файлы библиотеки-шаблона VS. Выбросить можно и последний dllmain.cpp - на работу остального не скажется
12. По причине избавления от пред-компиляции заголовочных файлов, мы можем выбросить базовые файлы библиотеки-шаблона VS. Выбросить можно и последний dllmain.cpp - на работу остального не скажется
13. Если сделать пункт 12, то не забыть поправить заголовочный файл wondows.h для стандартной инициации библиотеки
13. Если сделать пункт 12, то не забыть поправить заголовочный файл wondows.h для стандартной инициации библиотеки

Теперь надо инициализировать наш плагин с помощью стандартизированных процедур, описанных в справке к Renga SDK, для достижения чего сделать обращение к COM-библиотеке.

Удобно зафиксировать все заголовочные файлы в одном header-файле, в моем случае я назвал его "Renga_import.h".

Так выглядит заголовочный файл моего плагина, он же здесь https://github.com/GeorgGrebenyuk/renga_3d-export/blob/main/src/renga_core/plugin_start.h
Так выглядит заголовочный файл моего плагина, он же здесь https://github.com/GeorgGrebenyuk/renga_3d-export/blob/main/src/renga_core/plugin_start.h

В теле реализующего его cpp-файла есть дополнительные незадекларированные структуры: класс для отслеживания нажатий пользователя на кнопку. https://github.com/GeorgGrebenyuk/renga_3d-export/blob/main/src/renga_core/plugin_start.cpp.

2.3. Настройка статической библиотеки для консольного приложения

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

В моем случае это выглядит вот так
В моем случае это выглядит вот так
Реализация метода представлена вот в моем классе
Реализация метода представлена вот в моем классе

Компиляция библиотеки с такой конструкцией приведет к появлению нового файла *.lib среди скомпилированных файлов. Именно этот файл мы будем использовать как подключаемый к нашему консольному проекту для точки входа в приложение.

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

Согласен, выглядит топорно - но хотя бы работает ....

Для примера установим точку останова в тестовом классе и глянем на свойство проекта - путь к файлу.

Отладка приложения
Отладка приложения

3. Важные ограничения и некоторые мысли

1. Стоит отметить важную особенность - Renga при запуске инициализирует структуру библиотеки плагина и случается, крашится. Никаких логов не оставляет, но экспериментально было выявлено что причина крашей связана с линковкой файлов зависимостей библиотек - связанной с COM.

То есть в режиме отладки все работает отлично, а вот в режиме плагина она вообще не запускается.

2. На .NET пользователю щадяще оставлены .NET'овские типы данных, в то время как в C++ у нас их значительное многообразие - для одних только строк придется использовать различные конструкции типа char *, std::string, stg::wstring и пр.

3. Как я заметил, обращение с COM-библиотекой очень нестабильно (объяснить по другому краш Ренги при запуске я не могу), по причине чего желательно все нужные объекты свойств и геометрии желательно переводить в нейтральные классы, апеллирующие примитивами языка.

4. Вместо заключения

На этом я наверное пока останавливаюсь.

Могу, конечно еще упомянуть про попытку анализа крашей - при их возникновении формируются как минимум 2 файла

Отчет "Report.wer" xml-типа в генерируемой папке с путем типа такого: C:\ProgramData\Microsoft\Windows\WER\ReportArchive\AppCrash_Renga.exe_be83edeeda6066787737a9ab56190416fe579fc_2e055962_12116fa8-3ac3-4c3c-99cb-f9dc781557ac

И dump загруженных в память приложений по пути C:\Users\Georg\AppData\Local\CrashDumps с префиксом Renga.exe.

Ещё раз ссылаюсь на свой тестовый проект здесь - он будет посвящен логике преобразования объектов из 3D-представления Renga в популярные форматы моделей - Autodesk NWC и позже хочу ещё MSH (gmsh) и citygml.

GitHub - GeorgGrebenyuk/renga_3d-export: [In progress now!] Plugin to convert renga's model to other formats (such as NWC data format (for Autodesk Navisworks) and etc in future)

И да, в следующей части я попробую рассказать как получить всю геометрии модели вместе со свойствами и упаковать в структуры нового класса, на базе которого уже буду формироватах 3D-представление в других форматах.

Не пропускайте публикации, подписывайтесь на Telegram-канал с тизерами статей.

#renga #rengabim #com api #Ренга