Некоторые недавние работы заставили меня стать значительно более дружелюбным к разрешению нативных библиотек в macOS. Я хотел записать то, что я узнал, и вопрос на форуме Xamarin.Mac побудил меня найти время.

Прежде чем я начну, два предостережения:

  • Теоретически инструменты упаковки Xamarin.Mac должны справиться с этим за вас. Если вы столкнетесь с делами, которые, очевидно, должны работать и требуют ручной настройки, отправьте проблему в пример проекта.
  • Я далек от эксперта в этой области, и это обзор высокого уровня. Вы можете прочитать более подробную информацию здесь, а this - серьезная справочная страница. Мне нигде не удалось найти хороший общий обзор, поэтому я написал это. Не стесняйтесь оставлять исправления в комментариях.

Когда macOS загружает ваш исполняемый файл, он также загружает любые динамические библиотеки (и фреймворки), от которых напрямую зависит. В большинстве случаев это происходит без особого уведомления (и задолго до того, как ваш код запускается), но когда он терпит неудачу, вы получаете пугающее сообщение вроде этого:

dyld: Library not loaded: MyLibrary
  Referenced from: /path/to/my/application.app/Contents/MacOS/application
  Reason: image not found

Давайте разберемся, что происходит. В отличие от Windows, которая будет искать в нескольких местах, например, рядом с исполняемым файлом, macOS не будет (по умолчанию). В исполняемый файл встроены инструкции загрузчика, которым он точно следует. otool -l будет сбрасывать их в удобочитаемом формате.

В выводе будет перечислено большое количество команд загрузки, сегодня нас в первую очередь интересуют LC_LOAD_DYLIB и LC_RPATH.

  • LC_LOAD_DYLIB говорит: «Пожалуйста, загрузите родную библиотеку по этому пути». Это может быть абсолютный путь (/usr/lib/libSystem.B.dylib), относительный (../libFoo.dylib) или специальный (@ rpath / Foo.framework / Foo).
  • LC_RPATH добавляет элементы в rpath, которые мы вскоре опишем.

Абсолютные пути очевидны и являются общими только для системных библиотек. Относительные пути часто неверны, поскольку они основаны на вашем текущем каталоге (что означает, что если вы запустите его из другого места, он может не работать).

Часто вам нужно использовать местоположение относительно исполняемого файла, для чего предназначены некоторые из специальных путей с символом «@». Есть несколько вариантов:

@executable_path, @loader_path, @rpath

С этим - разумная ссылка.

rpath является наиболее важным для наших целей, потому что он говорит «ищите библиотеку в каждой папке, указанной с помощью команды LC_RPATH. Это позволяет, например, основному исполняемому файлу настроить rpath и зависимым библиотекам использовать его, не зная, где он обязательно находится.

Теперь, когда у вас есть общее представление о том, как библиотека загружает слова, как вы можете на это повлиять? Есть два распространенных подхода:

  • Передайте команды компоновщика в clang (часто в форме -Xlinker option -Xlinker value)
  • Используйте install_name_tool, чтобы изменить вещи после сборки.

install_name_tool имеет несколько параметров, но я обычно использую два:

install_name_tool -add_rpath @executable_path/. a.out

который, как это звучит, добавляет любой путь (в данном случае рядом с двоичным файлом) к моему a.out

install_name_tool -change libFoo @rpath/libFoo a.out

и это изменяет инструкцию загрузчика для libFoo со старого значения (libFoo) на новое (@ rpath / libFoo)

Есть также ряд переменных окружения, которые вы можете установить для получения исходной информации загрузчика. Иногда выгрузка списка того, какие библиотеки были разрешены из какого места:

DYLD_PRINT_LIBRARIES=1 ./Foo.app/Contents/MacOS/Foo

может быть поучительным.