macos_dylib_image

en Android, MacOS, Tutoriales, UE4

MacOS y librerías dinámicas

Más que un tutorial esto es solo una pequeña guía de como arreglar algunos problemas que se nos pueden presentar al enlazar librerías dinámicas en la plataforma MacOS, más concretamente los que aparecen al utilizar Unreal Engine 4 con esta plataforma.

Con la versión actual de Unreal Engine 4 (4.25) podemos empaquetar y utilizar el editor con librerias dinámicas sin nigún problema. También podemos utilizar plugins que carguen y utilizan librerías dinámicas de terceros.

Pero en ocasiones queremos utilizar una copia local de un plugin para nuestro proyecto, si por ejemplo queremos realizar una modificación en un plugin especifica solo para nuestro proyecto en vez de utilizar el plugin descargado del Marketplace podemos conseguirlo de esta manera.

Para ello vamos a necesitar convertir nuestro proyecto en un C++ Project y añadir una carpeta Plugin para copiar luego en ella el contenido del plugin que podemos encontrar en la carpeta de la instalación del Engine.

project_plugin

De esta manera la nuestro plugin local se compilará junto con nuestro proyecto.

Esta técnica puede solucionar algunos problemas relacionados con code-based plugins y el proceso de empaquetado de UE4, por ejemplo, empaquetando para Android y arquitectura arm64 con code-based plugins obtendremos un error similar a este cuando el juego se inicie.

arm64
Android error capture
Plugin ____ failed to load because module ____ could not be found. 
Please ensure the plugin is properly installed, otherwise consider disabling the plugin for this project.

Utilizar una copia del plugin dentro de la carpeta del projecto soluciona este error.

MacOS platform

Pero que ocurre con MacOS. Bien, irónicamente, la situación es la opuesta. Cuando se utiliza el proceso de empaquetado con el plugin desde el path de instalación del Engine la ejecución funciona correctamente, olo cuando se utilizada la copia local del plugin se presenta un problema de enlace con la librerías de terceros que utilice.

Mirando el log de una ejecución fallida podemos encontrarnos algo como esto:

Application Specific Information:
dyld: launch, loading dependent libraries

Dyld Error Message:
Library not loaded: @rpath/libSDL2.dylib
Referenced from: /Users/USER/Downloads/*/AA_MacLibrary.app/Contents/MacOS/AA_MacLibrary
Reason: image not found

La causa de este problema es que no ha sido capaz de encontrar la librería en ninguna de las rutas de busqueda definidas en el .app.

Un .app no es mas que una carpeta empaquetada, por lo tanto podemos ver su contenido desde el gestor de archivos y ver la estructura de carpetas del juego empaquetado. Aqui un ejemplo:

content_browser

Vemos que la copia del plugin ha sido corectamente empaquetada, luego el problema tiene que estar en las rutas de búsqueda.

Para ver las rutas de búsquedas que se han definido para nuestro empaquetado podemos utilizar el comando otool en un terminal junto con la ruta al ejecutable incluido dentro del .app, con el parámetro «-l» este comando nos mostrará los comandos de carga. Para nuestro ejemplo anterior:

otool -l ./pathToApp/AA_MacLibrary.app/Contents/MacOS/AA_MacLibrary

Esto mostrará un montón de información, peró en realidad solo nos interesan un par de secciones:

Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 48
         name @rpath/libSDL2.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1.0.0
compatibility version 1.0.0

La primera de ellas (LC_LOAD_DYLIB) es la seccion que define la ruta utilizada para cargar la librería.

El script de empaquetado de Unreal Engine utiliza @rpath para especificar las rutas de las librerías dinámicas. Con rpath el ejecutable buscará la librería en todos las rutas incluidas en la sección LC_RPATH. Asi que qué vamos a encontrar en esta sección:

Load command 51
          cmd LC_RPATH
      cmdsize 32
         path @loader_path/ (offset 12)
Load command 52
          cmd LC_RPATH
      cmdsize 32
         path @executable_path/ (offset 12)
Load command 53
          cmd LC_RPATH
      cmdsize 40
         path @executable_path/../../../ (offset 12)
Load command 54
          cmd LC_RPATH
      cmdsize 112
         path @loader_path/../../../../../Plugins/Runtime/MacLibraryLoader/Source/Thirdparty/SDL2/Libraries/Mac (offset 12)
Load command 55
          cmd LC_RPATH
      cmdsize 112
         path @loader_path/../../../../../Plugins/Runtime/MacLibraryLoader/Source/Thirdparty/SDL2/Libraries/Mac (offset 12)

@loader_path se resuelve como la ruta al ejecutable que hay dentro del .app, por lo tanto para nuestro ejemplo será como /AA_MacLibrary.app/Contents/MacOS/

Podemos ver que hay dos rutas de búsqueda que pueden ser las candidatas para nuestra librería, y ambas son idénticas, esto ya nos está indicando que algo no ha funcionado bien durante el proceso de empaquetado.

Si las miramos mas detenidamente nos damos cuenta de que esta ruta apunta fuera del paquete, por lo tanto, no va a funcionar en una maquina a no ser que tenga una copia del plugin en la ruta exacta que se indica.

Esta ruta puede funcionar bien mientras estamos desarrollando utilizando el Unreal Editor, pero no sirve en un juego empaquetado.

Para nuestro ejemplo una ruta correcta sería:

@loader_path/../UE4/AA_MacLibrary/Plugins/Marketplace/AudioAnalyzer/Source/Thirdparty/SDL2/Libraries/Mac

Esta ruta apunta a la carpeta Plugin que ya se ha incluido con el juego durante el proceso de empaquetado, podemos verla utilizando el gestor de archivos.

Afortunadamente podemos añadir más rutas de busqueda para las librerias al .app sin tener que reempaquetar el juego otra vez.

Para ello utilizaremos el comando install_name_tool con el parámetro -add_rpath

install_name_tool -add_rpath (SEARCH_PATH) (APP_PATH)

Para este ejemplo:

install_name_tool -add_rpath @loader_path/../UE4/AA_MacLibrary/Plugins/Marketplace/AudioAnalyzer/Source/Thirdparty/SDL2/Libraries/Mac ./AA_MacLibrary.app/Contents/MacOS/AA_MacLibrary

Tambien podríamos eliminar rutas de la lista utilizando el parámetro -delete_rpath en su lugar.

Ahora nuestro juego puede encontrar la librería que está dentro del .app y funciona perfectamente.

Este workaround puede resultar útil hasta que arreglen el script MacToolChain.cs para cubrir este caso.

Ayudanos con este blog!

El último año he estado dedicando cada vez más tiempo a la creación de tutoriales, en su mayoria sobre desarrollo de videojuegos. Si crees que estos posts te han ayudado de alguna manera o incluso inspirado, por favor considera ayudarnos a mantener este blog con alguna de estas opciones. Gracias por hacerlo posible!

Escribe un comentario

Comentario