Powershell: Le chargement des bibliothèques natives est interrompu

Créé le 27 avr. 2019  ·  70Commentaires  ·  Source: PowerShell/PowerShell

Tenter de charger un module qui nécessite p / invoke à partir d'une bibliothèque native tierce ne parvient pas à charger la bibliothèque à partir du même dossier que la DLL effectuant l'appel p /.

Cela _utilisait pour fonctionner_ dans PowerShell Core 6.1.0 mais a régressé entre 6.2.0-preview1 et 6.2.0-preview2. Cela fonctionne toujours dans Windows PowerShell.

La bibliothèque native qu'il ne parvient pas à localiser se trouve dans le même dossier que SkiaSharp.dll qui tente de la localiser. Voir https://github.com/PowerShell/PowerShell/issues/8861#issuecomment -462362391 et ce fil de discussion pour savoir pourquoi cela a été fait de cette façon.

Étapes à suivre pour reproduire

Install-Module PSWordCloud
New-WordCloud

Comportement prévisible

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

Comportement réel

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

Interroger l'exception de base avec $Error[0].Exception.GetBaseException() | Format-List * -F donne les résultats suivants:

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

Données d'environnement

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

_Note: _ La même chose a été testée dans un environnement Mac (merci @steviecoaster!) Et a également échoué de la même manière. Je soupçonne que ce sera également identique sur les distributions Linux.

/ cc @ SteveL-MSFT

Issue-Question Resolution-Fixed

Commentaire le plus utile

@rkeithhill

J'aurais vraiment aimé ne pas avoir à recompiler cela pour chaque plate-forme car il s'agit d'un assemblage netstandard2. Mais AFAICT la valeur de nom de lib fournie à l'attribut DllImport doit être une constante de temps de compilation. :-(

FWIW vous pouvez omettre l'extension pour DllImport . Nous faisons cela pour la dépendance native non Windows du PSES. Cela n'aide pas si les noms de base ne sont pas les mêmes.

Vous pouvez aussi probablement ignorer les directives du compilateur pour les appels LoadLibrary / dlopen en les stockant dans des implémentations d'interface séparées. Tant que la méthode n'est pas JIT, elle ne devrait pas être lancée au moment de l'exécution.

Tous les 70 commentaires

@adityapatwardhan pouvez-vous jeter un oeil à ça?

Vous pouvez rechercher les chemins utilisés avec l'utilitaire procmon , puis essayer de corriger avec HintPath

Je vais y jeter un coup d'œil, mais étant donné que ce n'est pas une bibliothèque que je charge directement (elle est chargée par le SkiaSharp.dll principal lui-même), je ne suis pas convaincu que cela fasse trop. Le fait qu'il existe quatre fichiers de bibliothèque possibles à charger en fonction de la plate-forme complique également les choses.

Mais quoi qu'il en soit, cela ne semble pas être un changement intentionnel, donc la régression devrait probablement être abordée.

L'équipe MSFT travaille dur sur vNext p / invokes dans .Net Core 3.0 et peut-être ont-ils rétroporté quelque chose vers 2.1. Je ne me souviens pas que nous ayons changé quoi que ce soit dans le chargement des dll liées à PowerShell.

Il y avait # 8073 et plusieurs changements marqués Internal dans 6.2.0-preview2. Je n'ai aucune idée de ce qui a pu changer pendant ces derniers. 😕

Le changement de PR consiste à charger d'abord la DLL du module à partir du dossier du module, puis à partir de GAC. Votre problème est que le module dll référence la deuxième dll et le chargement automatique de la dll (il ne fait pas Core pas PowerShell) ne trouve pas la dll. Ma suggestion est d'ajouter explicitement hintpath à la dll dans le fichier projet.

@iSazonov J'ai ajouté des références HintPath à mon csproj, mais tout ce que j'obtiens est un avertissement lorsque je dotnet publish . Il n'y a pas de changement de comportement du module une fois chargé.

Après quelques lectures, il semble que HintPath soit utilisé pour référencer des assemblys externes qui _ ne sont pas inclus_ dans la construction du projet lui-même. Les assemblys SkiaSharp sont inclus, donc l'ajout d'un HintPath pour la compilation / construction n'affecte pas les DLL Skia ou les chemins qu'ils recherchent au moment de l'exécution.

Pour référence, voici mon 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>

J'ai ajouté les lignes suivantes sans effet (autre qu'un avertissement de l'heure de construction / publication indiquant qu'elles ne peuvent pas être trouvées, car, eh bien, elles n'existent pas avant la fin de la publication de toute façon):

  <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>

D'après ce que j'ai lu sur HintPath, cela ne semble pas avoir d'effet sur les chemins recherchés lors du chargement d'une DLL, ou cette DLL essaie de charger d'autres DLL à des fins p / invoke.

_That_ HintPath est une chose uniquement à la compilation.

La façon dont tu dis ça donne l'impression qu'il y en a un autre que j'ai raté?

@ vexx32 Si vous avez du code de dépôt simple, vous pouvez le demander dans le référentiel CoreCLR.

Je ne sais pas si je peux exclure un changement spécifique à Powershell pour le moment. Je devrai peut-être faire un petit test pour une application console et voir comment cela se passe.

Grâce à un conseil de @Jaykul , j'ai réussi à mettre en place une solution

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)

Cependant, le fait qu'il _exige_ un appel p / supplémentaire pour faire ce travail est un peu ridicule. Je suis également très confus quant à ce qui a changé et supprimé le chemin du répertoire de SkiaSharp.dll chargé des emplacements de recherche standard comme documenté ici .

@ vexx32 Je n'ai pas pu reproduire le problème. Ai-je manqué quelque chose dans la repro? Peut-être une version spécifique 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

Désolé, comme mentionné, je l'ai rectifié dans 2.1.1.

Si vous installez 2.1.0 spécifiquement, vous verrez l'échec, car il manque la solution de contournement p / invoke ci-dessus.

Ok, je peux repro maintenant. J'irais regarder.

@ vexx32 Il semble que nous chargeons tous les fichiers listés dans FileList du psd1 . Avez-vous besoin de ça? Lorsque j'ai supprimé toutes les entrées de FileList je ne peux plus reproduire le problème.

Est-ce vrai? C'est très étrange. Non, je n'ai pas particulièrement besoin de cela et je vais probablement le supprimer de toute façon car je ne veux pas vraiment devoir le mettre à jour constamment, mais cela existe depuis quelques versions.

Cela semble étrange que cela affecte le chargement de la DLL tout d'un coup.

Il semble que nous chargeons tous les fichiers listés dans FileList du psd1.

Nous avons un problème pour prendre en charge "if" dans les fichiers .psd1. Nous pourrions donc charger des dll spécifiques à la plate-forme.

@iSazonov c'est # 5541 . Je vous donnerai bientôt une mise à jour.

C'est une possibilité. Ce que je me demande _ maintenant_, c'est si simplement ajouter le chemin aux bibliothèques natives (toutes) dans la FileList est suffisant ici ... Je pourrais même pas avoir besoin du p / invoke. Je vais faire quelques tests! 😊

EDIT: Non, je ne vois pas cela avoir d'effet. :(

Nous appelons Assembly.Load() qui ne charge que les assemblys gérés.

Nous avons apporté une modification dans 6.2.0-preview.2 où nous avons commencé à charger les DLL à partir du dossier du module avant de rechercher dans le GAC. L'effet secondaire de ce changement est que nous chargeons également des DLL dans FileList.

Ça a du sens. Avons-nous un problème de documentation ouvert pour cela? Il semble que ce genre de changement sera très difficile à retracer si quelqu'un y rencontre et que nous ne l'avons pas documenté. Nous devrions probablement documenter que vous ne voulez pas mettre d'assemblys natifs dans la liste de fichiers pour cette raison.

Appréciez l'aide pour le localiser! Pour l'instant, la solution P / invoke que j'ai mentionnée me donne le plus de flexibilité, mais il pourrait être judicieux d'ajouter la gestion de ces choses dans le chargement du module.

Par exemple, disons que dans PrivateData, nous ajoutons une entrée appelée RuntimeLibPaths, qui est une table de hachage où les clés correspondent aux noms / architectures de plate-forme pris en charge, et les valeurs sont le chemin vers le dossier contenant la bibliothèque native. Le ou les dossiers appropriés sont ajoutés aux chemins de recherche de dll lors de l'importation par PowerShell en interne plutôt que de devoir être ajoutés par le module lui-même lors de l'importation.

Avons-nous un problème de documentation ouvert pour cela?

Nous n'avons pas.

@adityapatwardhan @ SteveL-MSFT grâce à @TylerLeonhardt (involontairement) me rappelant que cela doit aussi fonctionner sur des choses non Windows si je veux être minutieux ... J'ai besoin d'une solution plus complète.

Il ne semble actuellement pas y avoir de documentation claire sur la façon dont l'interopérabilité native est censée être gérée dans .NET Core lui-même. Tout ce que je peux trouver, c'est cette page pour .NET Framework dans MS Docs.

Est-il géré via Mono ou quelque chose? Si c'est via Mono, en regardant cette page, il y a en fait un _bug dans Mono_ sur Mac OS qui empêche les chemins de chargement de la bibliothèque d'être correctement respectés ...

PowerShell ... doit gérer cela. Je ne suis pas sûr qu'il existe un moyen de gérer cela de manière appropriée à partir de toutes les plates-formes à partir du module lui-même.

@ vexx32 ne connaît pas les détails, si vous pouvez produire une repro en C # qui n'inclut pas PowerShell, je suggérerais d'ouvrir un problème dans le référentiel CoreCLR

@ steveL-msft c'est le problème, je pense. Cela semble bien fonctionner ailleurs, avec quelques mises en garde. Par exemple, le chargement de la bibliothèque native fonctionne si elle se trouve dans le même dossier que le SkiaSharp.dll principal, mais pour PS, cela ne fonctionne pas en raison du changement mentionné par @adityapatwardhan . Cela ne fait que multiplier la difficulté dans le cas de l'utilisation de PowerShell et de la façon dont il gère les modules.

Dans la plupart des situations, lorsque vous travaillez avec de telles bibliothèques natives, il peut être extrait, car vous pouvez de toute façon le conditionner pour chaque plateforme prise en charge. Nous n'avons tout simplement pas ce luxe pour les modules PS, à moins que je veuille empaqueter et maintenir au moins trois modules de galerie complètement séparés, ce qui est malheureusement vraiment mauvais pour la découvrabilité et l'expérience de l'utilisateur final.

Étant donné que PS est un outil multiplateforme, nous avons besoin d'un moyen de gérer les bibliothèques natives dans les modules, ou nous avons besoin d'un moyen pour que le module PackageManagement reconnaisse qu'il doit y avoir des bibliothèques séparées extraites sur une base par plate-forme à partir de nupkg.

Je suis ouvert à des solutions alternatives, mais il semble que le jeu ici soit assez rare. :confus:

@ vexx32 Je pense que vous voulez quelque chose comme ça https://github.com/PowerShell/PowerShellGet/issues/273

Dans CoreFX 3.0, nous obtenons de nouvelles API pour les bibliothèques natives Pinoke. Ils ne sont qu'au début de la route, mais ils veulent faire quelque chose d'encore mieux que Mono.

Actuellement, ma solution de contournement ressemble à ceci:

  1. Les bibliothèques natives pour Mac et Linux se trouvent à la racine du module, car cela semble être le seul endroit où je peux les mettre et les charger de manière fiable pour le moment.
  2. Un ScriptsToProcess gère p / invoke lors de l'importation du module pour ajouter la version 32 ou 64 bits de la bibliothèque native Windows, qui doit être stockée dans des sous-dossiers car les noms de fichiers sont malheureusement identiques.

Pas idéal, et comme @TylerLeonhardt peut en attester de son flux aujourd'hui, cela ne fonctionne toujours pas pour une raison quelconque lors de l'exécution dans Azure Functions pour le moment.

@ vexx32 ne connaît pas les détails, si vous pouvez produire une repro en C # qui n'inclut pas PowerShell, je suggérerais d'ouvrir un problème dans le référentiel CoreCLR

Non: en C # ce n'est pas un problème

En C #, vous:

  1. Développez une application et publiez des _installateurs distincts_ par plate-forme, et ne vous souciez que d'un _ chemin de recherche de bibliothèque_.
  2. Développez une bibliothèque et publiez _NuGet packages_, et nuget vous donne un modèle de chemin vous indiquant où placer vos bibliothèques, puis studio / msbuild s'occupe de tout pour vous.

@ vexx32 Je pense que vous voulez quelque chose comme ce PowerShell / PowerShellGet # 273

Non: ce n'est pas un problème multiplateforme

C'est pourquoi ce bug a été déposé. Depuis que PowerShell 6.2 @ vexx32 ne peut même pas obtenir une version qui fonctionne dans _juste Windows_, car il ne sait pas comment faire en sorte que PowerShell utilise le bon chemin de recherche de bibliothèque.

Dans Windows au moins, ap / invoke est disponible qui me permet de jouer avec. C'est une solution de contournement, mais nous avons vraiment besoin d'une meilleure solution.

Sur les systèmes Unix, cependant, un tel p / invoke_ n'est pas disponible, d'après ce que j'ai pu trouver, et pour une raison quelconque, PowerShell n'interroge pas les variables d'environnement qui indiquent normalement les chemins à rechercher lorsque vous essayez de DllImport à partir de un assemblage chargé par PS.

Donc, sur pratiquement toutes les plateformes, c'est ... extrêmement hostile de faire quoi que ce soit. 😅

Dans l'état actuel des choses, il ne semble y avoir aucune autre option que d'inclure tout et tous les assemblys natifs Unix directement dans le dossier racine du module (ce qui limite les plates-formes que vous pouvez prendre en charge, car il peut souvent y avoir des collisions de noms), puis de faire attention à laquelle dossier que vous ajoutez aux chemins de recherche sur Windows avec le p / invoke (si vous souhaitez prendre en charge Windows x86 - et @TylerLeonhardt me dit

C'est ce que j'utilise dans mon fichier psm1 pour précharger le binaire natif approprié pour la plate-forme. Remarque: je n'ai ciblé / testé ceci que sur Linux et Windows:

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

Et voici le code C # qui va dans 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 Vous pouvez ouvrir un problème de consultation dans le référentiel CoreCLR. Je suppose qu'ils ont ça à dire.

@rkeithhill cela ne fonctionne que pour les binaires .NET. J'ai besoin de charger un binaire .NET qui doit lui-même utiliser P / invoke pour charger un assembly natif. C'est là qu'il échoue et s'effondre complètement. Je n'ai aucun contrôle sur cette bibliothèque, c'est une bibliothèque tierce (SkiaSharp, dans ce cas). Je ne peux pas charger cette bibliothèque dans mon module binaire car elle doit être chargée en même temps que le code est chargé à partir de mon module, car mon module dépend des types uniquement présents et fonctionnels avec SkiaSharp chargé.

@isazonov, comme cela a été abordé plusieurs fois dans ce numéro maintenant, ce n'est pas un problème dans .net Core car vous pouvez simplement générer des packages séparés par plate-forme pour la plupart des applications.

PowerShell n'a aucun moyen possible de le faire, et il supprime accidentellement certains chemins standard des chemins de recherche de la bibliothèque, ce qui rend littéralement impossible de travailler avec des assemblys natifs dans certains contextes, et très fragile et particulier dans ceux qui se produisent. travail.

@ vexx32

J'ai besoin de charger un binaire .NET qui doit lui-même utiliser P / invoke pour charger un assembly natif. C'est là que échoue et s'effondre complètement.

Euh, c'est exactement ce que fait ce code. Je l'utilise avec succès sur 6.2 sur Windows et Linux depuis un moment maintenant.

Modifié, désolé, je n'ai pas été clair. Je ne peux pas faire cela dans mon propre binaire, les types doivent être présents car mon propre module est en cours de chargement; Je ne peux pas l'importer pendant l'instanciation de la commande ... Et je ne devrais pas avoir à le faire.

PowerShell ne devrait pas être moins fonctionnel que .net core lorsqu'il s'agit de charger des binaires.

Cela dit, je vais voir si je peux adapter certaines choses et améliorer mes solutions de contournement. Appréciez les exemples!

Mais vraiment ... je ne vois pas pourquoi je devrais devoir aller si loin pour contourner ce problème. Cela devrait juste fonctionner, et jusqu'à ce que PowerShell soit impliqué ... C'est le cas.

D'accord sur les cerceaux étant une douleur. J'essayais juste de montrer comment le chargement de la bibliothèque native peut être effectué via p / invoke sous Windows et Linux. :-)

@ vexx32 Je vois des divisions dans la discussion. Pourriez-vous préciser que c'est un problème - vous avez un module avec des bibliothèques natives et vous avez besoin de charger la bonne bibliothèque avec des dépendances sur une plate-forme spécifique?

Sorte de. C'est la mise en page que j'ai qui fonctionne actuellement, mais qui est compliquée comme tout. Tout ce que je ne peux pas définir de chemin via p / invoke doit être à la racine du module (ce qui pose des problèmes de collision de noms sur certaines plates-formes).

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

SkiaSharp.dll est .NET et est un assembly référencé pour ma dll d'applet de commande principale. Les quatre fichiers libSkiaSharp sont des DLL natives par plate-forme. Je ne contrôle pas le code dans SkiaSharp.dll, donc je ne peux pas forcer cette DLL à mettre à jour les chemins avant qu'elle n'appelle p /.

Alors ... j'ai un module avec une dépendance .NET. Cette dépendance dépend d'une bibliothèque native qu'elle charge avec DllImport; le fichier exact est différent par plate-forme / architecture (mais plusieurs fichiers ont des conflits de noms, ce qui limite les plates-formes que je peux réellement prendre en charge).

En utilisant le code de

_Idéalement_, TOUS ces fichiers devraient être au pire dans un sous-dossier à l'écart, quelque chose comme /PSWordCloud/runtime/<platform>/ - malheureusement, en raison de la façon dont PowerShell a changé les spécifications de chargement de chemin à partir des paramètres standard, seuls les fichiers à la racine dossier du module peut être chargé via DllImport. D'autres emplacements peuvent fonctionner, mais (au moins sur Mac / Linux) _toutes_ les variables d'environnement sur ces plates-formes qui sont censées définir des chemins de chargement d'exécution natifs sont complètement ignorées par PowerShell.

Je pense que $env:Path fonctionne toujours (même si cela ne devrait pas être utilisé sur les plates-formes Unix, et je ne l'ai pas testé depuis la version 6.2, donc cela pourrait aussi être cassé), mais franchement je ne le fais pas pense que c'est une bonne idée d'ajouter une tonne de chemins à cette variable, cela crée une expérience utilisateur vraiment terrible si l'utilisateur a besoin d'utiliser cette variable d'environnement. Vous chargez un module et soudainement, votre variable de chemin contient des données supplémentaires inattendues qui ne devraient probablement pas être là, mais _doit_ être là pour que les choses fonctionnent correctement.

Je pense que cela ne devrait pas être un problème; PowerShell devrait avoir une méthode plus robuste de gestion des chemins de chargement de bibliothèque natifs s'il va remplacer les méthodes prédéfinies de .NET Core. Je suggérerais d'avoir un moyen de spécifier un chemin de chargement de bibliothèque natif dans le manifeste du module ou quelque chose du genre, un moyen de définir où il devrait chercher par plate-forme lorsque quelque chose doit p / invoquer du code natif.

J'ai le même problème et le rapport de @ vexx32 contient tous les éléments de la chaîne.
Je suis d'accord que PowerShell devrait avoir une méthode plus robuste de gestion de la bibliothèque native.

Je ne pense pas du tout que ce soit une bonne idée d'ajouter une tonne de chemins à cette variable

D'accord et de même, je n'aime pas utiliser SetDllDirectory parce que c'est aussi le processus (pwsh). Le manifeste du module pourrait être mis à jour pour permettre aux auteurs de modules de fournir une liste de dll natives par combo os-arch, puis pwsh préchargerait ces dll natives avant de précharger les assemblys. Cela dit, si quelqu'un veut utiliser votre assembly à partir d'un autre langage, l'assembly devrait vraiment précharger ses propres dll natives. L'utilisateur de l'assemblage ne devrait pas avoir à faire cela IMO.

@ vexx32 Je "pense" que dlopen fonctionne également sur macOS.

Je vais essayer quand je peux (les VM mac sont un peu ... bizarres heh) mais de mémoire, Mac n'utilise pas les fichiers .so , donc je devrais certainement essayer - - la plupart des bibliothèques natives auxquelles je vois des références sur Mac semblent être .dylib .

L'assemblage fait son propre préchargement ici (du moins, je suis à peu près sûr qu'il le fait ...), je dois juste tout caser car l'assemblage ne sait pas où regarder avec la façon dont PS gère les chemins. : /

Je pense que macOS utilise .dylib , vous devrez donc compiler cet assemblage trois fois avec différentes macros définies, par exemple:

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      

J'aurais vraiment aimé ne pas avoir à recompiler cela pour chaque plate-forme car il s'agit d'un assemblage netstandard2. Mais AFAICT la valeur de nom de lib fournie à l'attribut DllImport doit être une constante de temps de compilation. :-(

Ouais. Comme il s'agit d'un module PowerShell, ce que je finirais par faire est une compilation dynamique à l'importation avec Add-Type -TypeDefinition $srcString avec une vérification de la plate-forme, puis la pointer vers le bon répertoire. Vous pouvez utiliser ScriptsToProcess dans un manifeste de module pour cela, heureusement.

De plus, libdl.so une bibliothèque valide et utilisable pour faire cela sur MacOS?

Mais oui, nous avons besoin d'une meilleure solution. 😄

@ vexx32 Avez-vous regardé Mono MapDll? La fonction permet un mappage automatique vers la bibliothèque native appropriée en fonction des conditions (arcade, plate-forme, etc.). Je m'attendrais à ce que vous en ayez besoin. Peut-être que SkiaSharp.dll devrait être recompilé mais il peut être configuré dans un fichier de configuration externe. Quelque chose de similaire sera implémenté dans Core.

Si je me souviens bien, .NET Core n'utilise pas Mono lors de l'exécution sous Windows, ce ne serait donc qu'une solution partielle.

J'apprécie beaucoup toutes les options proposées ici, et j'examinerai en détail quelles sont mes options actuelles - mais à la fin de la journée, PowerShell doit avoir quelque chose en place pour les modules multiplateformes à utiliser à cette fin à l'avenir. . 🙂

Si je me souviens bien, .NET Core n'utilise pas Mono lors de l'exécution sous Windows, ce ne serait donc qu'une solution partielle.

Je dis sur les nouvelles fonctionnalités. Voir
Document de conception Dllmap https://github.com/dotnet/coreclr/blob/c3e1bd5ccc482c9a7670762676bda95ebd34707d/Documentation/design-docs/dllmap.md
et version précédente du document
https://github.com/dotnet/coreclr/blob/6a48f05c77e80b604d71a9b84cc6b014306e849e/Documentation/design-docs/dllmap.md#

Mise à jour: CurrentContextualReflectionContext
https://github.com/dotnet/designs/blob/master/accepted/runtime-binding.md#rollforward

@rkeithhill

J'aurais vraiment aimé ne pas avoir à recompiler cela pour chaque plate-forme car il s'agit d'un assemblage netstandard2. Mais AFAICT la valeur de nom de lib fournie à l'attribut DllImport doit être une constante de temps de compilation. :-(

FWIW vous pouvez omettre l'extension pour DllImport . Nous faisons cela pour la dépendance native non Windows du PSES. Cela n'aide pas si les noms de base ne sont pas les mêmes.

Vous pouvez aussi probablement ignorer les directives du compilateur pour les appels LoadLibrary / dlopen en les stockant dans des implémentations d'interface séparées. Tant que la méthode n'est pas JIT, elle ne devrait pas être lancée au moment de l'exécution.

J'ai trouvé une solution possible à ce problème. Je devrais cependant l'essayer. Je suis actuellement occupé à travailler sur certaines versions, donc j'y arriverai probablement au début de la semaine prochaine. Je mettrai à jour ce problème avec mes résultats.

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 Cela ressemble à un wrapper multiplateforme autour de LoadLibrary / dlopen. Bonne trouvaille!

@adityapatwardhan J'ai trouvé ce problème autour de LoadUnmanagedDll. Peut-être sans rapport avec notre cas.

Nous devrions envisager d'ajouter un rappel permettant aux utilisateurs de fournir une stratégie de résolution personnalisée pour les bibliothèques non gérées. Nous avons l'intention de le faire avec AssemblyLoadContext.LoadUnmanagedDll. À la fin, il n'a résolu qu'une petite fraction d'entre eux, car cela ne fonctionne pas pour le contexte de chargement par défaut. Lorsque nous avons introduit AssemblyLoadContext.LoadUnmanagedDll à l'origine, nous avons également autorisé le remplacement du contexte de chargement par défaut, mais nous avons ensuite supprimé cette fonctionnalité et n'avons pas réfléchi à l'impact sur tous les scénarios.

Source: LoadUnmanagedDllFromPath / LoadUnmanagedDll ne fonctionne pas sous Linux si le fichier .so est présent dans un autre chemin que le répertoire de sortie de l'application

Je pense que mon lien ci-dessus sur les API NativeLibrary nous donne une solution multiplateforme (après le passage à .Net Core 3.0).

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

C'est pour les modèles plug-in. Cela pourrait résoudre le problème, # 6724, # 6426 et quelques autres.
/ cc @ daxian-dbw

@adityapatwardhan une mise à jour depuis le mois dernier? Nous voulons publier les bons conseils de documentation à ce sujet

Je n'ai pas pu me concentrer sur cela, je suis actuellement occupé avec d'autres articles hautement prioritaires. Je mettrai à jour d'ici la semaine prochaine.

@adamdriscoll pourrait peut-être donner quelques conseils car il a probablement dû faire des choses créatives dans UniversalDashboard et AvaloniaUI + PowerShell.

FWIW, le code que j'ai présenté ci - dlopen pinvoke et la modification du nom de la bibliothèque partagée sur mac, cela devrait fonctionner. C'est sur .NET Core 2.2.

UD fait à peu près la même chose que la solution de @rkeithhill . Cela fonctionne sur MacOSX ainsi que Linux et Windows. https://github.com/ironmansoftware/universal-dashboard/blob/master/src/UniversalDashboard/Server/CustomAssemblyLoadContext.cs

Le problème est qu'il pourrait y avoir beaucoup plus de variantes des chemins de dossier pour charger les différents binaires, donc c'est un peu un kluge. Si un nouveau type binaire est généré en fonction d'une nouvelle dépendance, un nouveau dossier peut apparaître et UD ne chargera pas l'assembly.

@rkeithhill fusionnez-vous tous les binaires WindowsLinux dans un seul dossier? (EDIT: en fait, je comprends. Vous avez un assembly DLL wrapper qui ne charge que l'assembly natif cible. Faites-vous cela pour tous vos assemblys natifs?)

UD conserve simplement le dossier d'exécution de publication comme lorsqu'il a été produit avec dotnet publish, puis la classe référencée essaie de charger tous les binaires natifs en fonction de l'architecture de plate-forme correcte.

Cela fonctionne mais j'aimerais que PS puisse simplement regarder un dossier d'exécution et charger les bons binaires. On dirait que dotnet doit faire ça quand il démarre alors peut-être qu'il y a du code à attraper là-bas.

Je change l'endroit où je recherche les assemblys natifs en fonction du résultat RuntimeInformation.IsOSPlatform(OSPlatform.Windows) . Si cela renvoie true, je charge à partir du dossier Windows répertoire dans lequel se trouve l'assembly C #. Sinon, je charge à partir du dossier Linux .

Faites-vous cela pour tous vos assemblys natifs?)

Oui, mais nous n'avons qu'une seule et cela ne dépend de rien d'autre que des bibliothèques système (msvcrt, etc.).

Pouvons-nous avoir une structure de fichier standard (peut-être basée sur les noms d'exécution)? Cela nous permettrait d'améliorer le résolveur de dll natif.

UD conserve simplement le dossier d'exécution de publication comme lorsqu'il a été produit avec dotnet publish, puis la classe référencée essaie de charger tous les binaires natifs en fonction de l'architecture de plate-forme correcte.

Donc, si je comprends bien, vous évitez d'utiliser PowerShell pour charger les binaires natifs en demandant simplement à .NET de les charger sous le capot lorsque vous chargez un binaire géré qui dépend des binaires natifs spécifiques au RID?

Cela fonctionne mais j'aimerais que PS puisse simplement regarder un dossier d'exécution et charger les bons binaires. On dirait que dotnet doit faire ça quand il démarre alors peut-être qu'il y a du code à attraper là-bas.

Ouais, je soupçonne qu'il y a une certaine prise de conscience RID dans le runtime .NET lui-même. J'ai essayé de nous éviter d'avoir à emprunter cette voie, mais il semble que nous devions le faire à un moment donné.

@joeyaiello

Donc, si je comprends bien, vous évitez d'utiliser PowerShell pour charger les binaires natifs en demandant simplement à .NET de les charger sous le capot lorsque vous chargez un binaire géré qui dépend des binaires natifs spécifiques au RID?

Oui exactement. UD charge simplement tout pour le RID cible. Ce n'est donc pas vraiment intelligent de savoir quelles sont les dépendances réelles. Je ne sais pas s'il existe un bon moyen de détecter cela.

@ vexx32 J'ai installé PSWordCloud. La structure des dossiers est-elle générale? Si c'est le cas, nous pouvons rendre notre résolveur plus intelligent. Dans le cas où je voudrais vous demander - pourriez-vous s'il vous plaît préparer de nouveaux tests Pester basés sur PSWordCloud simplifié (il nous suffit d'obtenir quelque chose de simple à partir des bibliothèques natives comme "Hello World")? Nous fusionnerions cela comme en attente et cela ouvrira la voie au travail sur notre résolveur. Merci!

Actuellement, la structure des dossiers de PSWordCloud n'est pas générale, non. Cela peut être changé, bien sûr.

Ouais, je peux absolument mettre en place un banc d'essai simple pour cela. Pester est un peu difficile à faire, juste en fonction de la nature de la sortie, je suppose, mais je pense que quand il échoue, il produit une exception que vous pouvez attraper, bien que cette exception soit levée par l'initialiseur de type si je me souviens bien.

Serait-il approprié que le test lui-même extrait les DLL Skia via dotnet, ou serait-il préférable de les empaqueter dans le dépôt? Je suppose que si nous les retirons à chaque fois, ce n'est pas si grave si cela est signalé comme un test de fonctionnalité une fois qu'il n'est plus en attente? :en pensant:

Si vous pouviez implémenter un nodule de test avec une dépendance indirecte avec les dll Skia, ce serait bien. Et oui, nous pourrions installer Skia à partir de nuget. Bien que le test soit plus fiable si nous plaçons directement les dll avec le module de test dans ~ notre dossier de module de test ~ un dossier d'assert.
Il semble que la meilleure pratique de Nuget consiste à placer des dll natives dans des dossiers nommés RID. Oui?

Oui, je peux faire ça! Je vais y jeter un coup d'œil demain. :rougir:

J'ai mis à jour mon commentaire - veuillez mettre le module de test dans un dossier d'assertion de test.

@rjmholt J'ai fait quelques tentatives pour utiliser le code que vous avez fourni pour le faire fonctionner pour PSWordCloud, mais cela ne fonctionne toujours pas du tout. L'opération de chargement se termine avec succès, mais le chargement du SkiaSharp.dll géré après que la bibliothèque non gérée ne parvient toujours pas à utiliser la DLL chargée sur les plates-formes Unix. Cela réussit à fonctionner pour Windows, d'une manière ou d'une autre.

Voir ma branche WIP ici https://github.com/vexx32/PSWordCloud/tree/FixPInvokes - exécutez build.ps1 pour construire le module et mettre tous les fichiers aux bons endroits. Il importera également le module pour vous. L'appel de New-WordCloud après l'exécution de la compilation fonctionne sur Windows, mais échoue complètement sur Mac OS.

@SeeminglyScience vous voyez quelque chose que j'ai fait de mal là-bas? : /

GitHub
Créez de jolis nuages ​​de mots avec PowerShell! Contribuez au développement de vexx32 / PSWordCloud en créant un compte sur GitHub.

@ SteveL-MSFT quel est le plan actuel pour gérer les bibliothèques natives? 😕

: tada: Ce problème a été résolu dans # 11032, qui a maintenant été publié avec succès sous le nom v7.0.0-rc.1 .: tada:

Liens utiles:

Cette page vous a été utile?
0 / 5 - 0 notes