Транслятор C# в C++: Операции над исходным кодом

На первый взгляд может показаться, что у транслятора может быть лишь один способ использования: подав ему на вход код C#, мы ожидаем получить на выходе эквивалентный код C++. Действительно, такой способ является наиболее распространённым, однако далеко не единственным. Ниже перечислены другие режимы, предоставляемые фреймворком для конвертации кода и связанными утилитами.

Анализ кода C# на портируемость

Продукты разрабатываются программистами, редко знающими в подробностях процедуру транслирования кода на другие языки и связанные с ней ограничения. В результате возникают ситуации, когда корректные с точки зрения C# изменения, сделанные продуктовыми разработчиками, ломают процедуру выпуска релизов для других языков.
За время развития проекта нами были испробованы несколько способов автоматизации обнаружения таких проблем:

  1. Проблема может быть обнаружена на этапе трансляции кода C# на C++, сборки транслированного кода C++ или прогона тестов. Это худший случай из возможных, поскольку обнаружение проблем происходит уже после слияния кода в основную ветку, зачастую – перед релизом. К тому же, решением проблемы занимаются люди, которые не в курсе изменений, вызвавших её, что увеличивает время на её устранение.
  2. Проблема может быть обнаружена в среде CI. Таким образом, о проблеме узнают разработчики C# перед слиянием в общую ветку или после такого слияния, в зависимости от принятых конкретной командой практик. Это увеличивает оперативность исправления проблем, но требует программирования дополнительных проверок в инфраструктуре транслятора или в сторонних утилитах.
  3. Проблема может быть обнаружена локально разработчиком C# при запуске специализированных инструментов – например, транслятора в режиме анализатора. Это удобно, но требует разработки дополнительных утилит и дисциплины.

Понижение версии языка C#

Ограничение версии языка C# требует дисциплины от программистов C# и во многих случаях неудобно. Чтобы обойти эти ограничения, трансляцию можно провести в два этапа: сначала заменить конструкции современных версий языка C# поддерживаемыми аналогами из прошлых стандартов, затем приступить непосредственно к трансляции.
При использовании транслятора, основанного на устаревшем синтаксическом анализаторе, понижение может быть выполнено только путём использования внешних инструментов (например, утилит, написанных на базе Roslyn). С другой стороны, трансляторы, основанные на Roslyn, выполняют оба этапа последовательно, что позволяет использовать один и тот же код как при транслировании кода ими, так и при подготовке кода к трансляции более старыми инструментами.

Подготовка примеров использования конвертированных библиотек

Это похоже на транслирование кода продуктов, однако подразумевает несколько иные требования. При транслировании библиотеки на десятки миллионов строк важно, прежде всего, максимально строгое следование поведению оригинального кода C# даже в ущерб читаемости – более простой, но отличающийся по эффектам код отлаживать придётся дольше. С другой стороны, примеры использования транслированного кода должны выглядеть максимально просто, давая понять, как пользоваться кодом в C++, даже если это не соответствует поведению оригинальных примеров, написанных на C#.
Так, при создании временных объектов программисты C# часто пользуются using statement, чтобы избежать утечки ресурсов и строго задать момент их высвобождения, не полагаясь на GC. Строгое транслирование using даёт достаточно сложный код C++ из-за множества нюансов вида "если в блоке using statement вылетает исключение и из Dispose() тоже вылетает исключение, какое из них попадёт в перехватывающий контекст?". Такой код лишь введёт в заблуждение программиста C++, создав впечатление, что использовать библиотеку сложно, однако на самом деле вполне достаточно умного указателя на стеке, в нужный момент удаляющего объект и высвобождающего ресурсы.

Подготовка документации к коду

Библиотеки, предоставляющие API, могут быть задокументированны через XML-комментарии в соответствии с практиками C#. Перенос комментариев в C++, например в формате Doxygen, отнюдь не тривиальная задача. Помимо разметки, необходимо заменить ссылки на типы (так как в C# полные имена записываются через точку, в C++ через пару двоеточий), их члены, а в случае использования свойств – ещё и понять, идёт ли речь о геттере или сеттере. Кроме того оттранслировать фрагменты кода, которые при этом лишены семантики и могут быть неполными.
Эта задача решается как средствами самого транслятора, так и внешними утилитами – например, анализирующими сгенерированную XML-документацию и дополнительно подготовленные фрагменты вроде примеров использования методов.

Заключение

Как мы видим, профессиональный фреймворк для конвертации кода, помимо качественного перевода кода C# на C++, должен уметь определять транслируемость исходного кода, при необходимости понижать языковую версию, транслировать примеры использования конвертируемых библиотек и их документацию.

Связанные статьи