26 января 2024
Разработка транслятора кода с C# на C++ были полностью выполнены компанией CodePorting. Работа потребовала многочисленных исследований, применения различных подходов и тестирования, учитывая модель памяти и другие аспекты. В итоге были выбраны два решения. Одно из них в настоящее время используется для выпуска продуктов Aspose для C++.
Настало время поговорить о технологиях, используемых в проекте. Портер представляет собой консольное приложение, написанное на языке C#, поскольку в таком виде его проще встраивать в скрипты, выполняющие задачи типа «портировать-скомпилировать-прогнать тесты». Кроме того, имеется GUI-компонент, позволяющий достигать тех же целей щелчками на кнопках.
Анализ синтаксиса в устаревшем поколении транслятора выполняется с помощью библиотеки NRefactory, а в новом – с использованием Roslyn.
Транслятор использует проходы по AST-дереву для сбора информации и генерации выходного кода на C++. При генерации кода C++ AST-представление не создаётся, и весь код сохраняется в виде простого текста.
Во многих случаях транслятору требуется дополнительная информация для тонкой настройки. Такая информация передаётся ему в виде опций и атрибутов. Опции применяются ко всему проекту сразу и позволяют задать, к примеру, имена макросов экспорта членов классов или определения препроцессора C#, используемые при анализе кода. Атрибуты навешиваются на типы и сущности и определяют обработку, специфичную для них. Например, необходимость генерации ключевых слов const
или mutable
для членов классов или исключения их из трансляции.
Классы и структуры C# транслируются в классы C++, их члены и исполняемый код транслируются в ближайшие эквиваленты. Generic-типы и методы отображаются на шаблоны C++. Ссылки C# транслируются в умные указатели (сильные или слабые), определённые в Библиотеке. Более подробно о принципах работы транслятора будет рассказано в отдельной статье.
Таким образом, исходная сборка C# преобразуется в проект на языке C++, который вместо библиотек .NET зависит от нашей общей библиотеки. Это показано на следующей диаграмме:
Для сборки библиотеки и транслированных проектов используется Cmake. На данный момент поддерживаются компиляторы VS 2017 и 2019 (Windows), GCC и Clang (Linux).
Как было сказано выше, большинство наших реализаций .NET представляют собой тонкие прослойки над сторонними библиотеками, выполняющими основную работу. Это включает в себя:
Как транслятор, так и библиотека покрыты многочисленными тестами. Тесты библиотеки используют фреймворк GoogleTest. Тесты транслятора написаны, в основном, на NUnit/xUnit и разбиваются на несколько категорий, удостоверяя, что:
Для хранения исходного кода мы используем GitLab. В качестве среды CI выбран Jenkins. Конвертированные продукты доступны в виде пакетов Nuget и в виде архивов для скачивания.
Во время работы над проектом нам пришлось столкнуться с большим количеством проблем. Одни из них были ожидаемы, тогда как другие проявились уже в процессе. Коротко перечислим основные из них:
Object
, а для большинства библиотечных классов отсутствует RTTI. Одно это уже ставит крест на возможности напрямую отобразить типы .NET на типы STL.yield
).using
, не справится с нашими задачами независимо от своего опыта по реализации рутинной бизнес-логики. Необходимость знать оба языка или готовность успешно разобраться в одним из них также снижает количество доступных кандидатов. С другой стороны, любители теории компиляторов или других экзотических дисциплин легко находят в проекте свою нишу.Несмотря на это, проект весьма интересен с технической стороны. Работа над ним позволяет многое узнать и многому научиться. Академичность задачи также способствует этому.
В рамках работы над проектом нам удалось реализовать систему, которая решает интересную академическую задачу ради её прямого практического применения. Мы организовали ежемесячный выпуск библиотек компании Aspose на языке, для которого они первоначально не предназначались. Оказалось, что большинство проблем вполне решаемо, а получившееся решение – надёжно и практично.
В скором времени планируется публикация ещё двух статей. В одной из них будет подробно, с примерами, рассказано о том, как работает транслятор и как конструкции C# отображаются на C++. В другой речь пойдёт о том, как нам удалось обеспечить совместимость моделей памяти двух языков.
Мы постараемся ответить на все заданные вопросы. Если читатели проявят интерес к другим аспектам разработки транслятора кода, мы можем рассмотреть возможность написания дополнительных статей на эту тему.