понедельник, 31 июля 2017 г.

Linux mono: DllNotFoundException & Dll mapping

Недавно возникла необходимость создать standalone-пекедж с приложением для Linux на mono. Традиционно эта задача решается очень просто: в папку с приложением накидываются все необходимые файлы, пути, где лежат библиотеки, добавляются в LD_LIBRARY_PATH (для обычных *.so-библиотек) и в MONO_PATH (для библиотек mono). Скрипт запуска будет выглядеть так:

export LD_LIBRARY_PATH=$PWD
export MONO_PATH=$PWD
./mono myprog.exe

И вроде бы все прекрасно, но постоянно валятся ошибки типа DllNotFoundException:

System.TypeInitializationException: The type initializer for 'System.Windows.Forms.XplatUI' threw an exception. ---> System.TypeInitializationException: The type initializer for 'System.Drawing.GDIPlus' threw an exception. ---> System.DllNotFoundException: gdiplus.dll

Причем есть два способа решения этой задачи. Первый способ это классический linux-way: просто создаем символическую ссылку с таким названием на нужную библиотеку (в моем случае это libgdiplus.so.0.0.0, например). Но это не очень хороший способ, т.к. нужно угадать с названием, которое может быть и gdiplus.dll.so и libgdiplus.so и еще какое-то. Лучше всего это узнать, включив дебаг:

MONO_LOG_LEVEL=debug  ./mono myprog.exe

и посмотрев, какие библиотеки вызывает mono-фрейворк.

Второй способ, на мой взгляд, лучше: использовать Dll-mapping. Для этого создадим файл System.Drawing.dll.config (поскольку именно библиотека System.Drawing.dll сгенерировала это исключение) следующего содержания (естественно, у вас имя нативного файла может отличаться):
<configuration>
  <dllmap dll="gdiplus.dll" target="libgdiplus.so.0.0.0"/>
</configuration>
Естественно, что для этого надо знать, в какой библиотеке (сборке) находится вызов, который требует ту или иную нативную библиотеку, иначе маппинг не будет работать. В чем преимущество такого способа? Ну например в том, что не надо гадать, какое имя ищет библиотека, когда говорит что не может найти какую-то *.dll (не говоря уже о том, что нативные *.dll-библиотеки под linux уже как-то запутывают, когда знаешь, что они должны быть с расширением *.so). Т.е. мы прямо указываем фреймворку, где искать нативную библиотеку и не пытаемся "угадывать" правильное название библиотеки.