воскресенье, 25 июля 2021 г.

extern "C" и namespaces в C/C++ или как я подгорел на выходных

За что я люблю и ненавижу C++ одновременно - так это за то, что он может заставить себя почувствовать дураком и нубом себя в любой момент работы с ним.

TL;DR никогда ни за что, не мешайте extern "C" и пространства имён в определениях функций. Это не работает нормально ни в одном компиляторе и, как обычно, это описано только где-то в дебрях stackoverflow, хоть и разрешено в компиляторах MinGW и MSVC . 

Замечу, что эта проверка есть в SonarSource но почему-то в виденных мной учебниках плюсов это правило отсутствует, что принципиально неправильно.

Логичный вопрос - а зачем это вообще мне понадобилось? Начну с оригинальной проблемы. Дело, в том, что в Saddy нет как-таковой поддержки MinGW x64. И это ай-яй-яй как хочется. А почему нет? Потому что у библиотеки от которой он частично зависит в паре плагинов  - irrKlang - нет билдов по MinGW x64. Ну вот нет их и всё.  И я долго ждал, уже подумывал менять библиотеку, чтобы как-то обеспечить билды под этот вариант, но недавно набрёл на то, что можно слинковаться с DLL от MSVC используя команды вида

gendef a.dll

dlltool -v -d a.def  -l liba.a

 где gendef  - это тула, которая по умолчанию не идёт с MinGW которая создаёт DEF-файлы для DLL. Её легко собрать, ничего сложного в этом нет, главное чтобы она была нужной разрядности Выглядит просто?

Не совсем так. Библиотека сгенерируется, но вот ABI у MinGW и MSVC сильно отличаются для C++ и поэтому методы будут внутри заmangleны по-другому. И  vtable тоже будут отличаться, что позволит виртуальным вызовам счастливо падать. Зато ABI одинаков в C API, поэтому если скомпилировать DLL с C API в MSVC, а потом, через метод выше, подлинковать с MinGW, то всё будет работать.  В результате получается логичный план - обернуть irrKlang API в DLL с C API и так всё собирать, после чего использовать. вместо оригинальных методов сишные.  Главное - из унаследованных классов не кидать исключения вовне, а то это тоже не совместимо.

Первый вариант такого API был прост - заворачиваем всё в extern "C" и внутри размещаем по namespace на каждый класс irrKlang с соответствующими функциями, внутри дёргаем оригинальные с правильными параметры. Это работает с MSVC, MinGW. Первый - точно игнорирует пространства имён, так что коллизии по именам возможны, но это работает. И в MinGW работает (хотят там GCC).  А вот GCC 11 на Ubuntu 20.04, в упор ругался на реализацию функции, которая находится в пространстве имён что она должна в нём находиться, но не находится (irrklangProxy::XXX::YYY must be in namespace irrklangProxy::XXX::YYY). Это одно из самых невнятных сообщений компилятора, что я видел и я видел некоторое дерьмо, поверьте мне. К этому моменту в библиотеке были уже все нужные методы и довольно много кода, который не хотелось переписывать вручную.

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

  1. Почему такие конструкции изначально не запрещены комитетом? Ну, come on, это же просто пройтись по синтаксическому дереву и  выкинуть ошибку, a.k.a "Don't mix namespaces and extern "C"" Выглядит несложно и куда понятнее ошибки выше. Это же не какие-то лямбды, что первая, что вторая фича живут с нами ещё с C++-03 и позволяют отстрелить себе ногу.
  2. Почему об этом не написано на каждом заборе? Вот как перечислить фичи языка - за этим просто очередь, как о таком писать, а об этом НАДО писать, то только stackoverflow и гугл спасают.
  3. Когда у нас будут нормальные UTF-8 строки в STL, без головной боли? Уже везде они есть, даже в Ruby уже 10 версий вышло, а плюсовики всё с огрызком вместо (это не относится к посту)
На оба вопроса в голове рождается очевидный ответ, который не хочется здесь приводить.

По библиотеке, в конечном итоге, я написал небольшой uglifier на PHP (не буду приводить, писался за полчаса и код там просто отвратителен), который перевёл пространства имён в некрасивые префиксы, а дублирующим вызовам из пространства обеспечил inline реализацию с вызовом функций  с некрасивым префиксом. В итоге выпустил первый вариант библиотеки лишь сегодня.

Так и живём. Не повторяйте моих ошибок.

Комментариев нет:

Отправить комментарий