Powershell: La carga de bibliotecas nativas no funciona

Creado en 27 abr. 2019  ·  70Comentarios  ·  Fuente: PowerShell/PowerShell

Al intentar cargar un módulo que requiere p / invoke desde una biblioteca nativa de terceros, no se carga la biblioteca desde la misma carpeta que la DLL que realiza la invocación p /.

Esto _ solía funcionar_ en PowerShell Core 6.1.0 pero retrocedió entre 6.2.0-preview1 y 6.2.0-preview2. Todavía funciona en Windows PowerShell.

La biblioteca nativa que no logra ubicar se encuentra en la misma carpeta que SkiaSharp.dll que está tratando de ubicarla. Consulte https://github.com/PowerShell/PowerShell/issues/8861#issuecomment -462362391 y ese hilo de problemas para saber por qué se hizo de esa manera.

pasos para reproducir

Install-Module PSWordCloud
New-WordCloud

Comportamiento esperado

cmdlet New-WordCloud at command pipeline position 1
Supply values for the following parameters:
InputObject:

Comportamiento real

new-wordcloud : The type initializer for 'PSWordCloud.WCUtils' threw an exception.
At line:1 char:1
+ new-wordcloud
+ ~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], TypeInitializationException
+ FullyQualifiedErrorId : System.TypeInitializationException

La consulta de la excepción base con $Error[0].Exception.GetBaseException() | Format-List * -F produce lo siguiente:

Message        : Unable to load DLL 'libSkiaSharp' or one of its dependencies: The specified module could not be
                 found. (Exception from HRESULT: 0x8007007E)
TypeName       :
Data           : {}
InnerException :
TargetSite     : IntPtr sk_fontmgr_ref_default()
StackTrace     :    at SkiaSharp.SkiaApi.sk_fontmgr_ref_default()
                    at SkiaSharp.SKFontManager.get_Default()
                    at PSWordCloud.WCUtils..cctor()
HelpLink       :
Source         : SkiaSharp
HResult        : -2146233052

Datos ambientales

Name                           Value
----                           -----
PSVersion                      6.2.0
PSEdition                      Core
GitCommitId                    6.2.0
OS                             Microsoft Windows 10.0.17763
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

_Nota: _ Lo mismo se probó en un entorno Mac (¡gracias @steviecoaster!) Y también falló de la misma manera. Sospecho que también será idéntico en las distribuciones de Linux.

/ cc @ SteveL-MSFT

Issue-Question Resolution-Fixed

Comentario más útil

@rkeithhill

Realmente desearía no tener que volver a compilar esto para cada plataforma porque este es el ensamblaje netstandard2. Pero AFAICT, el valor del nombre lib proporcionado al atributo DllImport tiene que ser una constante de tiempo de compilación. :-(

FWIW puede omitir la extensión para DllImport . Hacemos eso para la dependencia nativa de PSES que no es de Windows. Sin embargo, no ayuda si los nombres base no son los mismos.

Probablemente también pueda omitir las directivas del compilador para las llamadas LoadLibrary / dlopen almacenándolas en implementaciones de interfaz separadas. Siempre que el método no sea JIT, no debería lanzarse en tiempo de ejecución.

Todos 70 comentarios

@adityapatwardhan, ¿puedes echarle un vistazo a esto?

Puede buscar rutas usadas con la utilidad procmon y luego intentar arreglar con HintPath

Lo echaré un vistazo, pero dado que esta no es una biblioteca que estoy cargando directamente (está cargada por el propio SkiaSharp.dll principal), no estoy tan seguro de que haga demasiado. El hecho de que haya cuatro posibles archivos de biblioteca para cargar según la plataforma también complica las cosas.

Pero independientemente, esto no parece un cambio intencional, por lo que probablemente debería abordarse la regresión.

El equipo de MSFT trabaja arduamente en las invocaciones de vNext p / en .Net Core 3.0 y tal vez hicieron una copia de respaldo de algo a 2.1. No recuerdo que hayamos cambiado nada en la carga de dll relacionados con PowerShell.

Hubo # 8073 y varios cambios marcados con Internal en 6.2.0-preview2. No tengo idea de qué más pudo haber cambiado durante esos. 😕

El cambio de PR es cargar el dll del módulo desde la carpeta del módulo primero y luego desde GAC. Su problema es que el dll del módulo hace referencia al segundo dll y la carga automática de dll (no Core, no PowerShell) no encuentra el dll. Mi sugerencia es agregar explícitamente hintpath al dll en el archivo del proyecto.

@iSazonov Agregué algunas referencias HintPath a mi csproj, pero todo lo que recibo es una advertencia cuando dotnet publish . No hay ningún cambio en el comportamiento del módulo una vez cargado.

A partir de un poco de lectura, parece que HintPath se usa para hacer referencia a ensamblajes externos que _no están incluidos_ en la compilación del proyecto. Los ensamblados de SkiaSharp están incluidos, por lo que agregar un HintPath para compilar / construir no afecta las DLL de Skia o las rutas que buscan en tiempo de ejecución.

Como referencia, este es mi csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="All" />
    <PackageReference Include="SkiaSharp" Version="1.68.0" />
    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
  </ItemGroup>

</Project>

Agregué las siguientes líneas sin ningún efecto (aparte de una advertencia de tiempo de compilación / publicación de que no se pueden encontrar, porque, bueno, no existen hasta que se completa la publicación de todos modos):

  <ItemGroup>
    <Reference Include="libSkiaSharp">
        <HintPath>win-x86/libSkiaSharp.dll</HintPath>
        <HintPath>win-x64/libSkiaSharp.dll</HintPath>
        <HintPath>osx/libSkiaSharp.dylib</HintPath>
        <HintPath>linux-x64/libSkiaSharp.dylib</HintPath>
    </Reference>
</ItemGroup>

Por lo que he leído en HintPath, no parece tener ningún efecto sobre las rutas que se buscan al cargar una DLL, o esa DLL intenta cargar otras DLL para fines de invocación / p.

_Eso_ HintPath es solo una cosa en tiempo de compilación.

¿La forma en que dices eso hace que parezca que hay otro que me perdí?

@ vexx32 Si tiene un código de repositorio simple, puede preguntar en el repositorio de CoreCLR.

No estoy seguro de poder descartar un cambio específico de Powershell por el momento. Es posible que tenga que hacer una pequeña prueba para una aplicación de consola y ver cómo funciona.

Gracias a un consejo de @Jaykul , me las arreglé para armar una solución

Add-Type -TypeDefinition @"
    using System.Runtime.InteropServices;

    public class DllLoadPath
    {
        [DllImport("kernel32", CharSet=CharSet.Unicode)]
        public static extern int SetDllDirectory(string NewDirectory);
    }
"@

# ...

[DllLoadPath]::SetDllDirectory($NativeRuntimeFolder)

Sin embargo, el hecho de que _requiere_ p / invoke adicional para que esto funcione es algo ridículo. También estoy muy confundido en cuanto a lo que cambió y eliminó la ruta del directorio de SkiaSharp.dll cargada de las ubicaciones de búsqueda estándar como se documenta aquí .

@ vexx32 No pude reproducir el problema. ¿Me perdí algo en la reproducción? ¿Quizás una versión específica si PSWordCloud?

PS> Install-Module PSWordCloud                                                               
Untrusted repository
You are installing the modules from an untrusted repository. If you trust this repository, change its
InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from
'PSGallery'?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): y

PS> New-WordCloud                                                                            
cmdlet New-WordCloud at command pipeline position 1
Supply values for the following parameters:
InputObject:
Path:

PS> $PSVersionTable                                                                          
Name                           Value
----                           -----
PSVersion                      6.2.0
PSEdition                      Core
GitCommitId                    6.2.0
OS                             Microsoft Windows 10.0.18885
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Lo siento, como mencioné lo rectifiqué en 2.1.1.

Si instala 2.1.0 específicamente, verá el error, ya que carece de la solución p / invoke anterior.

Ok, puedo reprogramar ahora. Echaré un vistazo.

@ vexx32 Parece que FileList del psd1 . ¿Necesitas tener eso? Cuando eliminé todas las entradas de FileList no puedo volver a reproducir el problema.

¿Es eso así? Eso es muy extraño. No, no lo necesito particularmente y probablemente lo eliminaré de todos modos, ya que realmente no quiero tener que actualizarlo constantemente, pero eso ha estado ahí durante algunas versiones.

Sin embargo, parece extraño que eso afecte la carga de DLL de repente.

Parece que cargamos todo el archivo listado en FileList del psd1.

Tenemos un problema para admitir "if" en archivos .psd1. Entonces podríamos cargar archivos DLL específicos de la plataforma.

@iSazonov es el # 5541 . Te daré una actualización pronto.

Esa es una posibilidad. Lo que me pregunto _ahora_ es si simplemente agregar la ruta a las bibliotecas nativas (todas) en FileList es suficiente aquí ... Es posible que ni siquiera necesite la invocación p /. ¡Haré algunas pruebas! 😊

EDITAR: No, no veo que eso tenga ningún efecto. :(

Llamamos Assembly.Load() que solo carga ensamblados administrados.

Hicimos un cambio en 6.2.0-preview.2 donde comenzamos a cargar archivos DLL desde la carpeta del módulo antes de buscar en el GAC. El efecto secundario de ese cambio es que también cargamos archivos DLL en FileList.

Eso tiene sentido. ¿Tenemos un problema de documentación abierto para eso? Parece que este tipo de cambio será muy difícil de rastrear si alguien se encuentra con él y no lo hemos documentado. Probablemente deberíamos documentar que no desea poner ensamblados nativos en la lista de archivos por ese motivo.

¡Apreciamos la ayuda para rastrearlo! Por ahora, la solución P / invoke que mencioné me brinda la mayor flexibilidad, pero podría ser sensato agregar manejo para tales cosas en la carga del módulo.

Por ejemplo, digamos que en PrivateData agregamos una entrada llamada RuntimeLibPaths, que es una tabla hash en la que las claves coinciden con los nombres / arquitecturas de la plataforma admitida, y los valores son la ruta a la carpeta que contiene la biblioteca nativa. Las carpetas apropiadas se agregan a las rutas de búsqueda de dll en la importación mediante PowerShell internamente en lugar de tener que ser agregadas por el propio módulo en la importación.

¿Tenemos un problema de documentación abierto para eso?

No lo hemos hecho.

@adityapatwardhan @ SteveL-MSFT gracias a @TylerLeonhardt (sin querer) recordándome que esto también tiene que funcionar en cosas que no son de Windows si quiero ser minucioso ... Necesito una solución más completa.

Actualmente no parece haber ninguna documentación clara sobre cómo se supone que se maneja la interoperabilidad nativa en .NET Core. Todo lo que puedo encontrar es esta página para .NET Framework en MS docs.

¿Se maneja a través de Mono o algo así? Si es a través de Mono, mirando esta página, en realidad hay un _bug en Mono_ en Mac OS que impide que las rutas de carga de la biblioteca se respeten correctamente ...

PowerShell ... necesita manejar esto. No estoy seguro de que haya una manera de manejar esto adecuadamente desde todas las plataformas desde el módulo en sí.

@ vexx32 no conoce los detalles, si puede producir una reproducción en C # que no incluya PowerShell, sugeriría abrir un problema en el repositorio de CoreCLR

@ steveL-msft ese es el problema, creo. Parece funcionar bien en otros lugares, con algunas advertencias todavía. Por ejemplo, cargar la biblioteca nativa funciona si está en la misma carpeta que el SkiaSharp.dll principal, pero para PS eso no funciona debido al cambio que menciona @adityapatwardhan . Eso solo multiplica la dificultad en el caso de trabajar con PowerShell y cómo maneja los módulos.

En la mayoría de las situaciones, al trabajar con estas bibliotecas nativas, se puede abstraer, porque de todos modos puede empaquetarlo para cada plataforma compatible. Simplemente, no tenemos ese lujo para los módulos de PS, a menos que quiera empaquetar y mantener tres o más módulos de galería completamente separados, lo que desafortunadamente es realmente malo para la visibilidad y la experiencia del usuario final.

Dado que PS es una herramienta multiplataforma, necesitamos alguna forma de manejar las bibliotecas nativas en módulos, o necesitamos alguna forma de que el módulo PackageManagement reconozca que es necesario que haya bibliotecas separadas extraídas por plataforma del nupkg.

Estoy abierto a soluciones alternativas, pero parece que la baraja aquí es bastante escasa. :confuso:

@ vexx32 Creo que quieres algo como esto https://github.com/PowerShell/PowerShellGet/issues/273

En CoreFX 3.0 obtenemos algunas API nuevas para las bibliotecas nativas de pinoke. Están solo al comienzo del camino, pero quieren hacer algo incluso mejor que Mono.

Actualmente, mi solución alternativa se ve así:

  1. Las bibliotecas nativas para Mac y Linux están en la raíz del módulo, ya que ese parece ser el único lugar donde puedo colocarlas y cargarlas de manera confiable en este momento.
  2. Un ScriptsToProcess maneja p / invoke en la importación del módulo para agregar la versión de 32 o 64 bits de la biblioteca nativa de Windows, que debe almacenarse en subcarpetas ya que los nombres de archivo son, desafortunadamente, idénticos.

No es ideal, y como @TylerLeonhardt puede dar fe de su transmisión de hoy, esto todavía no funciona por alguna razón cuando se ejecuta en Azure Functions en este momento.

@ vexx32 no conoce los detalles, si puede producir una reproducción en C # que no incluya PowerShell, sugeriría abrir un problema en el repositorio de CoreCLR

No: en C # esto no es un problema

En C # usted:

  1. Desarrolle una aplicación y libere _ instaladores separados_ por plataforma, y ​​solo tenga que preocuparse por _una ruta de búsqueda de biblioteca_.
  2. Desarrolle una biblioteca y libere _NuGet packages_, y nuget le brinda un patrón de ruta que le indica dónde colocar sus bibliotecas, y luego studio / msbuild se encarga de todo por usted.

@ vexx32 Creo que quieres algo como este PowerShell / PowerShellGet # 273

No: este no es un problema multiplataforma

Por eso se archivó este error. Desde PowerShell 6.2 @ vexx32 ni siquiera puede obtener una versión que funcione en _sólo Windows_ porque no puede averiguar cómo hacer que PowerShell use la ruta de búsqueda de biblioteca correcta.

En Windows, al menos hay un ap / invoke disponible que me permite jugar con él. Esa es una _ solución alternativa_ pero realmente necesitamos una solución mejor.

En los sistemas Unix, sin embargo, _no hay tal p / invoke_ disponible, por lo que he podido encontrar, y por alguna razón PowerShell no está consultando las variables de entorno que normalmente indican qué rutas buscar cuando intenta DllImport desde un conjunto cargado por PS.

Así que básicamente en todas las plataformas esto es ... extremadamente hostil para hacer algo. 😅

Tal como está actualmente, parece que no hay otra opción aparte de incluir todos los ensamblados nativos de Unix directamente en la carpeta raíz del módulo (lo que limita las plataformas que puede admitir, ya que a menudo puede haber colisiones de nombres), y luego tener cuidado con cuál carpeta que agrega a las rutas de búsqueda en Windows con p / invoke (si le importa admitir Windows x86, y @TylerLeonhardt me dice que Azure Functions se ejecuta en x86 por alguna extraña razón, por lo que es una necesidad si desea trabajar con aquellos.)

Esto es lo que uso en mi archivo psm1 para precargar el binario nativo apropiado para la plataforma. Nota: Solo apunté / probé esto en Linux y Windows:

# Module PSM1 File
$binPath = Join-Path $PSScriptRoot bin $(if ($IsWindows) {"Windows"} else {"Linux"})
Add-Type -Path $binPath\Acme.Api.dll

Y este es el código C # que va en Acme.Api.dll

[Flags]
public enum DLOpenFlags
{
    RTLD_LAZY = 1,
    RTLD_NOW = 2,
    RTLD_LOCAL = 4,
    RTLD_GLOBAL = 8,
}

public static class LinuxNativeMethods
{
    [DllImport("libdl.so", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    public static extern IntPtr dlopen(
        string dlToOpen, 
        DLOpenFlags flags);
}

public static class Win32NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint="LoadLibraryW")]
    public static extern IntPtr LoadLibrary(
        [InAttribute, MarshalAs(UnmanagedType.LPWStr)] string dllToLoad);
}

public static class FooNativeMethods
{
    // On Linux/macOS the shared lib must be named Foo.so and not libFoo.so
    public const string LibName = "Foo";

    static FooNativeMethods()
    {
        string assemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 
        {
            string nativeDllPath = Path.Combine(assemblyDir, "Windows", $"{LibName}.dll");
            LibHandle = Win32NativeMethods.LoadLibrary(nativeDllPath);
        }
        else
        {
            string nativeSOPath = Path.Combine(assemblyDir, "Linux", $"{LibName}.so");
            LibHandle = LinuxNativeMethods.dlopen(nativeSOPath, DLOpenFlags.RTLD_LAZY);
        }
    }

    public static IntPtr LibHandle { get; private set; }

    [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
    public static extern int getLastError(
        StringBuilder buffer,
        ref int bufferLength);

    ...
}

@ vexx32 Podría abrir un problema de consultoría en el repositorio de CoreCLR. Supongo que tienen eso que decir.

@rkeithhill esto solo funciona para binarios .NET. Necesito cargar un binario .NET que necesita usar P / invoke para cargar un ensamblado nativo. Ahí es donde falla y se desmorona por completo. No tengo control sobre esta biblioteca, es una biblioteca de terceros (SkiaSharp, en este caso). No puedo cargar esta biblioteca en mi módulo binario, ya que debe cargarse al mismo tiempo que se carga el código desde mi módulo, ya que mi módulo depende de los tipos que solo están presentes y funcionan con SkiaSharp cargado.

@isazonov, como ya se ha tratado un par de veces en este número, este no es un problema en .net Core porque simplemente puede generar paquetes separados por plataforma para la mayoría de las aplicaciones.

PowerShell no tiene ninguna forma posible de hacer esto, e incidentalmente elimina algunas rutas estándar de las rutas de búsqueda de la biblioteca, lo que hace que sea literalmente imposible trabajar con ensamblados nativos en algunos contextos, y muy frágil y particular en los que sí ocurren. trabajo.

@ vexx32

Necesito cargar un binario .NET que necesita usar P / invoke para cargar un ensamblado nativo. Ahí es donde falla y se desmorona por completo.

Uh, eso es exactamente lo que hace este código. Lo he estado usando con éxito en 6.2 tanto en Windows como en Linux desde hace un tiempo.

Modificado, lo siento, no estaba claro. No puedo hacer esto en mi propio binario, los tipos deben estar presentes mientras se carga mi propio módulo; No puedo importarlo durante la instanciación del comando ... Y no debería tener que hacerlo.

PowerShell no debería ser menos funcional que .net core cuando se trata de cargar binarios.

Dicho esto, veré si puedo adaptar algunas cosas y mejorar mis soluciones. ¡Aprecia los ejemplos!

Pero realmente ... no veo por qué debería tener que hacer todo lo posible para solucionar esto. Simplemente debería funcionar, y hasta que PowerShell se involucre ... lo hace.

Estuvo de acuerdo en que los aros eran un fastidio. Solo estaba tratando de mostrar cómo se puede realizar la carga de la biblioteca nativa a través de p / invoke en Windows y Linux. :-)

@ vexx32 Veo dividirse en la discusión. ¿Podría aclarar que es un problema: tiene un módulo con bibliotecas nativas y necesita cargar la biblioteca correcta con dependencias en una plataforma específica?

Algo así como. Este es el diseño que tengo que funciona actualmente, pero es desordenado como diablos. Cualquier cosa para la que no pueda definir una ruta a través de p / invoke debe estar en la raíz del módulo (lo que tiene problemas de colisión de nombres en algunas plataformas).

PSWordCloud/
|- PSWordCloudCmdlet.dll
|- SkiaSharp.dll
|- libSkiaSharp.so
|- libSkiaSharp.dylib
|- win-x86/
  |- libSkiaSharp.dll
|- win-x64/
  |- libSkiaSharp.dll

SkiaSharp.dll es .NET y es un ensamblado al que se hace referencia para mi dll de cmdlet principal. Los cuatro archivos libSkiaSharp son archivos DLL nativos por plataforma. No controlo el código en SkiaSharp.dll, por lo que no puedo obligar a esa DLL a actualizar las rutas antes de que p / invoca.

Entonces ... tengo un módulo con una dependencia de .NET. Esta dependencia depende de una biblioteca nativa que se está cargando con DllImport; el archivo exacto es diferente según la plataforma / arquitectura (pero varios de los archivos tienen colisiones de nombres, por lo que restringe las plataformas que realmente puedo admitir).

Usando el código de @rkeithhill , creo que también puedo mover la biblioteca nativa de Linux a una subcarpeta. No estoy seguro sobre MacOS, probablemente haya algo similar, pero todavía tengo que encontrar documentación sobre métodos para eso.

_Idealmente_, TODOS estos archivos deberían estar en el peor de los casos en una subcarpeta fuera del camino, algo así como /PSWordCloud/runtime/<platform>/ - desafortunadamente, debido a cómo PowerShell ha cambiado las especificaciones de carga de la ruta de la configuración estándar, solo archivos en la raíz La carpeta del módulo se puede cargar a través de DllImport. Es posible que otras ubicaciones funcionen, pero (al menos en Mac / Linux) _todas_ las variables de entorno en aquellas plataformas que se supone que definen rutas de carga nativas en tiempo de ejecución son completamente ignoradas por PowerShell.

Creo que $env:Path todavía funciona (aunque no debería usar eso en plataformas Unix, y no lo he probado desde la versión 6.2, por lo que también podría estar roto), pero francamente no lo hago Creo que es una buena idea agregar un montón de rutas a esa variable, lo que hace que la experiencia del usuario sea realmente terrible si el usuario necesita usar esa variable de entorno. Cargas un módulo y, de repente, tu variable de ruta tiene datos extra inesperados que probablemente no deberían estar allí, pero _tiene_ que estar allí para que las cosas funcionen correctamente.

Creo que esto no debería ser un problema; PowerShell debería tener un método más sólido para manejar las rutas de carga de la biblioteca nativa si va a anular los métodos predefinidos de .NET Core. Sugeriría tener alguna forma de especificar una ruta de carga de biblioteca nativa en el manifiesto del módulo o algo por el estilo, alguna forma de definir dónde debería estar buscando por plataforma cuando algo necesita p / invocar algún código nativo.

Tengo el mismo problema y el informe de @ vexx32 contiene todos los elementos de la cadena.
Estoy de acuerdo en que PowerShell debería tener un método más sólido para manejar la biblioteca nativa.

No creo que sea una buena idea agregar un montón de rutas a esa variable

De acuerdo y del mismo modo, no estoy interesado en usar SetDllDirectory porque ese también es un proceso amplio (pwsh). El manifiesto del módulo podría actualizarse para permitir que los autores del módulo proporcionen una lista de dlls nativos por combo os-arch y luego pwsh precargaría esos dlls nativos antes de precargar cualquier ensamblado. Dicho esto, si alguien va a usar su ensamblador desde otro lenguaje, el ensamblado realmente debería precargar sus propias DLL nativas. El usuario del ensamblaje no debería tener que hacer eso en mi opinión.

@ vexx32 "Creo" que dlopen funciona en macOS.

Lo intentaré cuando pueda (las máquinas virtuales de mac son un poco ... raras, je) pero desde la memoria, Mac no usa archivos .so , así que definitivamente tendría que intentarlo - - la mayoría de las bibliotecas nativas a las que veo referencias en Mac parecen ser .dylib .

El ensamblaje hace su propia precarga aquí (al menos, estoy bastante seguro de que lo hace ...), solo tengo que aplicar un caso especial a todo porque el ensamblaje no sabe dónde buscar con la forma en que PS maneja las rutas. : /

Creo que macOS usa .dylib por lo que necesitaría compilar ese ensamblaje tres veces con diferentes macros definidas, por ejemplo:

public static class FooNativeMethods
{
    // This unfortunately requires that you compile this assembly twice - once with WIN32 defined and again with
    // it not defined.
#if WIN32        
    public const string LibName = "Foo";
#elif MACOS
    public const string LibName = "libFoo.dylib";
#else
    public const string LibName = "libFoo.so";
#endif      

Realmente desearía no tener que volver a compilar esto para cada plataforma porque este es el ensamblaje netstandard2. Pero AFAICT, el valor del nombre de lib proporcionado al atributo DllImport tiene que ser una constante de tiempo de compilación. :-(

Si. Dado que este es un módulo de PowerShell, lo que en realidad terminaría haciendo es una compilación dinámica en la importación con Add-Type -TypeDefinition $srcString con una verificación de plataforma y luego apuntar al directorio correcto. Afortunadamente, puede usar ScriptsToProcess en un manifiesto de módulo para esto.

Además, ¿es libdl.so una biblioteca válida y utilizable para hacer esto en MacOS?

Pero sí, necesitamos una mejor solución. 😄

@ vexx32 ¿Te veías en Mono MapDll? La función permite el mapeo automático a la biblioteca nativa apropiada según las condiciones (arco, plataforma, etc.). Espero que sea lo que necesitas. Quizás SkiaSharp.dll deba recompilarse, pero se puede configurar en un archivo de configuración externo. Algo similar se implementará en Core.

Si mal no recuerdo, .NET Core no usa Mono cuando se ejecuta en Windows, por lo que nuevamente sería solo una solución parcial.

Aprecio mucho todas las opciones que se ofrecen aquí, y examinaré a fondo cuáles son mis opciones actuales, pero al final del día, PowerShell necesita tener algo en su lugar para que los módulos multiplataforma se utilicen para este propósito en el futuro. . 🙂

Si mal no recuerdo, .NET Core no usa Mono cuando se ejecuta en Windows, por lo que nuevamente sería solo una solución parcial.

Digo sobre nuevas funciones. Ver
Documento de diseño de Dllmap https://github.com/dotnet/coreclr/blob/c3e1bd5ccc482c9a7670762676bda95ebd34707d/Documentation/design-docs/dllmap.md
y versión anterior del documento
https://github.com/dotnet/coreclr/blob/6a48f05c77e80b604d71a9b84cc6b014306e849e/Documentation/design-docs/dllmap.md#

Actualización: CurrentContextualReflectionContext
https://github.com/dotnet/designs/blob/master/accepted/runtime-binding.md#rollforward

@rkeithhill

Realmente desearía no tener que volver a compilar esto para cada plataforma porque este es el ensamblaje netstandard2. Pero AFAICT, el valor del nombre lib proporcionado al atributo DllImport tiene que ser una constante de tiempo de compilación. :-(

FWIW puede omitir la extensión para DllImport . Hacemos eso para la dependencia nativa de PSES que no es de Windows. Sin embargo, no ayuda si los nombres base no son los mismos.

Probablemente también pueda omitir las directivas del compilador para las llamadas LoadLibrary / dlopen almacenándolas en implementaciones de interfaz separadas. Siempre que el método no sea JIT, no debería lanzarse en tiempo de ejecución.

Encontré una posible solución para este problema. Sin embargo, tendría que probarlo. Actualmente estoy ocupado trabajando en algunos lanzamientos, así que lo más probable es que llegue a esto a principios de la semana que viene. Actualizaré este problema con mis hallazgos.

https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext.loadunmanageddll?view=netcore-2.2#System_Runtime_Loader_AssemblyLoadContext_LoadUnmanagedDll_System_String_

@adityapatwardhan Parece un contenedor multiplataforma alrededor de LoadLibrary / dlopen. ¡Buen hallazgo!

@adityapatwardhan Encontré este problema en LoadUnmanagedDll. Quizás irrelevante para nuestro caso.

Deberíamos considerar agregar una devolución de llamada que permita a la gente proporcionar una política de resolución personalizada para bibliotecas no administradas. Tenemos la intención de hacer eso con AssemblyLoadContext.LoadUnmanagedDll. Al final, solo abordó una pequeña fracción del problema debido a que no funciona para el contexto de carga predeterminado. Cuando presentamos AssemblyLoadContext.LoadUnmanagedDll originalmente, permitimos anular el contexto de carga predeterminado también, pero luego eliminamos esta capacidad y no pensamos en el impacto en todos los escenarios.

Fuente: LoadUnmanagedDllFromPath / LoadUnmanagedDll no funciona en linux si el archivo .so está presente en cualquier otra ruta que no sea el directorio de salida de la aplicación

Creo que mi enlace anterior sobre las API de NativeLibrary nos brinda una solución multiplataforma (después de pasar a .Net Core 3.0).

CurrentContextualReflectionContext https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md

Es para modelos enchufables. Esto podría resolver el problema, # 6724, # 6426 y algunos otros.
/ cc @ daxian-dbw

@adityapatwardhan alguna actualización desde el mes pasado? Queremos publicar la guía de documentos adecuada sobre este

No he podido concentrarme en esto, actualmente estoy ocupado con otros elementos de alta prioridad. Actualizaré en algún momento de la próxima semana.

@adamdriscoll podría dar algunos consejos, ya que probablemente tuvo que hacer algunas cosas creativas en UniversalDashboard y AvaloniaUI + PowerShell.

FWIW, el código que presenté anteriormente es el que usamos para un módulo interno que usa una biblioteca nativa. Funciona sin problemas en Windows y Linux. No lo he probado en macOS, pero aparte de los ajustes al dlopen pinvoke y cambiar el nombre de la biblioteca compartida en mac, debería funcionar. Esto está en .NET Core 2.2.

UD está haciendo prácticamente lo mismo que la solución de @rkeithhill . Esto funciona tanto en MacOSX como en Linux y Windows. https://github.com/ironmansoftware/universal-dashboard/blob/master/src/UniversalDashboard/Server/CustomAssemblyLoadContext.cs

El problema es que podría haber muchas más variaciones de las rutas de las carpetas para cargar los diversos binarios, por lo que es un poco complicado. Si se genera un nuevo tipo binario basado en una nueva dependencia, puede aparecer una nueva carpeta y UD no cargará el ensamblaje.

@rkeithhill, ¿está fusionando todos los binarios de WindowsLinux en una sola carpeta? (EDITAR: En realidad, lo entiendo. Tiene un ensamblaje de DLL de contenedor que solo carga el ensamblado nativo de destino. ¿Hace esto para todos sus ensamblados nativos?)

UD simplemente mantiene la carpeta de ejecución de publicación igual que cuando se produjo con dotnet publish y luego la clase referenciada intenta cargar todos los binarios nativos basados ​​en la arquitectura de plataforma correcta.

Funciona, pero desearía que PS pudiera simplemente mirar una carpeta en tiempo de ejecución y cargar los binarios correctos. Parece que dotnet debe estar haciendo eso cuando se inicia, así que tal vez haya algún código para atrapar allí.

Cambio dónde busco ensamblados nativos según el resultado RuntimeInformation.IsOSPlatform(OSPlatform.Windows) . Si eso devuelve verdadero, cargo desde la carpeta Windows del directorio en el que se encuentra el ensamblado C #. De lo contrario, cargo desde la carpeta Linux .

¿Hace esto para todos sus ensamblados nativos?)

Sí, pero solo tenemos uno y no depende de nada más que las bibliotecas del sistema (msvcrt, etc.).

¿Podemos tener alguna estructura de archivo estándar (tal vez basada en nombres de tiempo de ejecución)? Esto nos permitiría mejorar la resolución nativa de dll.

UD simplemente mantiene la carpeta de ejecución de publicación igual que cuando se produjo con dotnet publish y luego la clase referenciada intenta cargar todos los binarios nativos basados ​​en la arquitectura de plataforma correcta.

Entonces, si entiendo correctamente, ¿está evitando usar PowerShell para cargar los binarios nativos simplemente haciendo que .NET los cargue bajo el capó cuando carga un binario administrado que depende de los binarios nativos específicos de RID?

Funciona, pero desearía que PS pudiera simplemente mirar una carpeta en tiempo de ejecución y cargar los binarios correctos. Parece que dotnet debe estar haciendo eso cuando se inicia, así que tal vez haya algún código para atrapar allí.

Sí, sospecho que hay algo de conciencia de RID dentro del tiempo de ejecución de .NET. He intentado evitar que tengamos que seguir ese camino, pero parece que es posible que lo necesitemos en algún momento.

@joeyaiello

Entonces, si entiendo correctamente, ¿está evitando usar PowerShell para cargar los binarios nativos simplemente haciendo que .NET los cargue bajo el capó cuando carga un binario administrado que depende de los binarios nativos específicos de RID?

Si, exacto. UD simplemente carga todo para el RID de destino. Por lo tanto, no es realmente inteligente cuáles son las dependencias reales. No estoy seguro de si hay una buena manera de detectar eso.

@ vexx32 Instalé PSWordCloud. ¿La estructura de carpetas es general? Si es así, podemos hacer que nuestro resolutor sea más inteligente. En el caso que quiero preguntarle, ¿podría preparar nuevas pruebas de Pester basadas en PSWordCloud simplificado (es suficiente para nosotros obtener algo simple de las bibliotecas nativas como "Hello World")? Fusionaríamos eso como pendiente y abrirá el camino para trabajar en nuestro resolutor. ¡Gracias!

Actualmente, la estructura de carpetas de PSWordCloud no es general, no. Eso se puede cambiar, por supuesto.

Sí, absolutamente puedo armar un banco de pruebas simple para eso. Pester es un poco complicado de hacer, solo en función de la naturaleza de la salida, supongo, pero creo que cuando falla produce una excepción que puede detectar, aunque esa excepción se lanza desde el inicializador de tipo si recuerdo correctamente.

¿Sería apropiado que la prueba extraiga las DLL de Skia a través de dotnet, o sería mejor empaquetarlas en el repositorio? Supongo que si los sacamos cada vez no es tan importante si se marca como una prueba de función una vez que ya no está pendiente. :pensando:

Sería bueno si pudiera implementar un nódulo de prueba con dependencia indirecta con Skia dlls. Y sí, podríamos instalar Skia desde nuget. Aunque la prueba sería más confiable si colocamos directamente los dlls con el módulo de prueba en ~ nuestra carpeta de módulo de prueba ~ una carpeta de aserción.
Parece que la mejor práctica de Nuget es colocar archivos DLL nativos en carpetas con nombre RID. ¿Si?

¡Sí, puedo hacer eso! Lo echaré un vistazo mañana. :sonrojo:

Actualicé mi comentario; coloque el módulo de prueba en una carpeta de confirmación de prueba.

@rjmholt Hice algunos intentos de usar el código que proporcionaste para que funcione para PSWordCloud, pero todavía no funciona en absoluto. La operación de carga se completa con éxito, pero la carga del SkiaSharp.dll administrado después de que la biblioteca no administrada aún no hace uso de la DLL cargada en las plataformas Unix. Sin embargo, se las arregla para funcionar para Windows, de alguna manera.

Vea mi rama WIP aquí https://github.com/vexx32/PSWordCloud/tree/FixPInvokes - ejecute build.ps1 para construir el módulo y poner todos los archivos en los lugares correctos. También importará el módulo por ti. Llamar a New-WordCloud después de ejecutar la compilación funciona en Windows, pero falla completamente en Mac OS.

@SeminglyScience, ¿ves algo que hice mal allí? : /

GitHub
¡Crea bonitas nubes de palabras con PowerShell! Contribuya al desarrollo de vexx32 / PSWordCloud creando una cuenta en GitHub.

@ SteveL-MSFT ¿cuál es el plan para manejar bibliotecas nativas en la actualidad? 😕

: tada: este problema se abordó en # 11032, que ahora se ha publicado con éxito como v7.0.0-rc.1 .: tada:

Enlaces útiles:

¿Fue útil esta página
0 / 5 - 0 calificaciones