Powershell: Das Laden nativer Bibliotheken ist fehlerhaft

Erstellt am 27. Apr. 2019  ·  70Kommentare  ·  Quelle: PowerShell/PowerShell

Beim Versuch, ein Modul zu laden, für das p / invoke aus einer nativen Bibliothek eines Drittanbieters erforderlich ist, wird die Bibliothek nicht aus demselben Ordner geladen wie die DLL, die p / invoking ausführt.

Dies funktionierte in PowerShell Core 6.1.0, ging jedoch zwischen 6.2.0-Vorschau1 und 6.2.0-Vorschau2 zurück. Es funktioniert immer noch in Windows PowerShell.

Die native Bibliothek, die nicht gefunden werden kann, befindet sich im selben Ordner wie die SkiaSharp.dll, die versucht, sie zu finden. Unter https://github.com/PowerShell/PowerShell/issues/8861#issuecomment -462362391 und diesem Problem-Thread erfahren Sie, warum dies auf diese Weise geschehen ist.

Schritte zum Reproduzieren

Install-Module PSWordCloud
New-WordCloud

Erwartetes Verhalten

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

Tatsächliches Verhalten

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

Das Abfragen der Basisausnahme mit $Error[0].Exception.GetBaseException() | Format-List * -F ergibt Folgendes:

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

Umgebungsdaten

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

_Hinweis: _ Dasselbe wurde in einer Mac-Umgebung getestet (danke @steviecoaster!) Und ist auch auf die gleiche Weise fehlgeschlagen. Ich vermute, dass es auch unter Linux-Distributionen identisch sein wird.

/ cc @ SteveL-MSFT

Issue-Question Resolution-Fixed

Hilfreichster Kommentar

@ Rkeithhill

Ich wünschte wirklich, ich müsste dies nicht für jede Plattform neu kompilieren, da dies eine netstandard2-Assembly ist. AFAICT Der für das DllImport-Attribut angegebene lib-Namenswert muss jedoch eine Kompilierungszeitkonstante sein. :-(

FWIW können Sie die Erweiterung für DllImport weglassen. Wir tun dies für die native Abhängigkeit von PSES, die nicht von Windows

Sie können wahrscheinlich auch die Compiler-Direktiven für die Aufrufe LoadLibrary / dlopen überspringen, indem Sie sie in separaten Schnittstellenimplementierungen speichern. Solange die Methode nicht JIT-fähig ist, sollte sie zur Laufzeit nicht ausgelöst werden.

Alle 70 Kommentare

@adityapatwardhan kannst du dir das ansehen?

Sie können gebrauchte Pfade mit dem Dienstprogramm procmon suchen und dann versuchen, sie mit HintPath zu reparieren

Ich werde das mal ansehen, aber da dies keine Bibliothek ist, die ich direkt lade (sie wird von der Haupt-SkiaSharp.dll selbst geladen), bin ich nicht so zuversichtlich, dass ich zu viel mache. Die Tatsache, dass je nach Plattform vier mögliche Bibliotheksdateien geladen werden können, erschwert die Sache ebenfalls.

Unabhängig davon scheint dies keine absichtliche Änderung zu sein, weshalb die Regression wahrscheinlich angegangen werden sollte.

Das MSFT-Team arbeitet hart an vNext p / invokes in .Net Core 3.0 und hat möglicherweise etwas auf 2.1 zurückportiert. Ich erinnere mich nicht, dass wir beim Laden von PowerShell-bezogenen DLLs etwas geändert haben.

Es gab # 8073 und mehrere Internal -markierte Änderungen in 6.2.0-Vorschau2. Ich habe keine Ahnung, was sich währenddessen noch geändert haben könnte. 😕

Die PR-Änderung besteht darin, die Modul-DLL zuerst aus dem Modulordner und dann aus dem GAC zu laden. Ihr Problem ist, dass die Modul-DLL auf die zweite DLL verweist und das automatische Laden der DLL (Core nicht PowerShell) die DLL nicht gefunden hat. Mein Vorschlag ist, der DLL in der Projektdatei explizit einen Hinweispfad hinzuzufügen.

@iSazonov Ich habe einige HintPath-Verweise zu meinem csproj hinzugefügt, aber alles, was ich bekomme, ist eine Warnung, wenn ich dotnet publish . Nach dem Laden ändert sich das Verhalten des Moduls nicht.

Nach einigem Nachlesen sieht es so aus, als würde der HintPath verwendet, um auf externe Assemblys zu verweisen, die nicht im Projektbuild selbst enthalten sind. Die SkiaSharp-Assemblys sind enthalten, sodass das Hinzufügen eines HintPaths zum Kompilieren / Erstellen keine Auswirkungen auf die Skia-DLLs oder die Pfade hat, nach denen sie zur Laufzeit suchen.

Als Referenz ist dies mein 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>

Ich habe die folgenden Zeilen ohne Wirkung hinzugefügt (abgesehen von einer Warnung zur Erstellungs- / Veröffentlichungszeit, dass sie nicht gefunden werden können, da sie ohnehin erst nach Abschluss der Veröffentlichung vorhanden sind):

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

Nach dem, was ich auf HintPath gelesen habe, scheint es keinen Einfluss darauf zu haben, welche Pfade beim Laden einer DLL durchsucht werden oder dass die DLL versucht, andere DLLs für p / invoke-Zwecke zu laden.

_That_ HintPath ist nur eine Sache zur Kompilierungszeit.

Wie du sagst, klingt es so, als hätte ich noch einen vermisst?

@ vexx32 Wenn Sie einfachen Repo-Code haben, können Sie im CoreCLR-Repository nachfragen.

Ich bin mir nicht sicher, ob ich eine Powershell-spezifische Änderung noch ausschließen kann. Möglicherweise muss ich einen kleinen Test für eine Konsolen-App zusammenstellen und sehen, wie das geht.

Dank eines Tipps von @Jaykul habe ich es geschafft, eine Stop-Gap-Lösung zusammenzustellen. Das Ausführen von PSM1 vor dem Importieren des Cmdlets und der Haupt-Skia-DLL scheint das Problem zu beheben.

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)

Die Tatsache, dass es zusätzliches p / invoke erfordert, um diese Arbeit zu machen, ist jedoch irgendwie lächerlich. Ich bin auch sehr verwirrt darüber, was den geladenen Verzeichnispfad von SkiaSharp.dll geändert und aus den hier dokumentierten Standardsuchpositionen entfernt

@ vexx32 Ich konnte das Problem nicht reproduzieren. Habe ich etwas im Repro verpasst? Vielleicht eine bestimmte Version von 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

Entschuldigung, wie bereits erwähnt, habe ich es in 2.1.1 korrigiert.

Wenn Sie 2.1.0 speziell installieren, wird der Fehler angezeigt, da die oben beschriebene Problemumgehung für p / invoke fehlt.

Ok, ich kann jetzt repro. Ich werde nachsehen.

@ vexx32 Es scheint, dass wir alle Dateien laden, die in FileList der psd1 . Müssen Sie das haben? Wenn ich alle Einträge aus FileList habe, kann ich das Problem nicht mehr wiederholen.

Ist das so? Das ist sehr seltsam. Nein, das brauche ich nicht besonders und werde es wahrscheinlich trotzdem entfernen, da ich es nicht wirklich ständig aktualisieren möchte, aber das gibt es schon seit einigen Versionen.

Scheint seltsam, dass sich dies plötzlich auf das Laden der DLL auswirken würde.

Es scheint, dass wir alle in FileList der psd1 aufgelisteten Dateien laden.

Wir haben ein Problem mit der Unterstützung von "if" in .psd1-Dateien. So konnten wir plattformspezifische DLLs laden.

@iSazonov es ist # 5541 . Ich werde Sie bald aktualisieren.

Das ist eine Möglichkeit. Was ich mich jetzt frage, ist, ob das einfache Hinzufügen des Pfads zu den nativen Bibliotheken (alle) in der Dateiliste hier ausreicht ... Ich brauche möglicherweise nicht einmal den p / -Aufruf. Ich werde ein paar Tests machen! 😊

EDIT: Nein, ich sehe keine Auswirkungen. :(

Wir nennen Assembly.Load() , das nur verwaltete Assemblys lädt.

Wir haben in 6.2.0-Preview.2 eine Änderung vorgenommen, bei der wir begonnen haben, DLLs aus dem Modulordner zu laden, bevor wir im GAC nachgeschlagen haben. Der Nebeneffekt dieser Änderung ist, dass wir auch DLLs in FileList laden.

Das macht Sinn. Haben wir dafür ein Dokumentproblem offen? Es scheint, als ob diese Art von Veränderung sehr schwer zu finden sein wird, wenn jemand darauf stößt und wir sie nicht dokumentiert haben. Wir sollten wahrscheinlich dokumentieren, dass Sie aus diesem Grund keine nativen Assemblys in die Dateiliste aufnehmen möchten.

Schätzen Sie die Hilfe, die es aufspürt! Im Moment bietet mir die von mir erwähnte P / Invoke-Lösung die größte Flexibilität, aber es könnte sinnvoll sein, die Handhabung für solche Dinge beim Laden von Modulen hinzuzufügen.

Angenommen, wir fügen in PrivateData einen Eintrag mit dem Namen RuntimeLibPaths hinzu. Hierbei handelt es sich um eine Hashtabelle, in der die Schlüssel mit den unterstützten Plattformnamen / -architekturen übereinstimmen. Die Werte sind der Pfad zu dem Ordner, der die native Bibliothek enthält. Die entsprechenden Ordner werden beim Import von PowerShell intern zu den DLL-Suchpfaden hinzugefügt, anstatt beim Import vom Modul selbst hinzugefügt werden zu müssen.

Haben wir dafür ein Dokumentproblem offen?

Wir haben nicht.

@adityapatwardhan @ SteveL-MSFT dank @TylerLeonhardt (ungewollt), der mich daran erinnert, dass dies auch bei Nicht-Windows-Dingen funktionieren muss, wenn ich gründlich sein möchte ... Ich brauche eine umfassendere Lösung.

Derzeit scheint es keine klare Dokumentation darüber zu geben, wie native Interop in .NET Core selbst behandelt werden soll. Ich kann nur diese Seite für .NET Framework in MS-Dokumenten finden.

Wird es über Mono oder so gehandhabt? Wenn es über Mono geht, gibt es auf dieser Seite tatsächlich einen Fehler in Mono unter Mac OS, der verhindert, dass die Ladepfade der Bibliothek ordnungsgemäß eingehalten werden ...

PowerShell ... muss damit umgehen. Ich bin nicht sicher, ob es eine Möglichkeit gibt, dies von allen Plattformen des Moduls selbst angemessen zu handhaben.

@ vexx32 kennt die Details nicht. Wenn Sie in C # einen Repro erstellen können, der PowerShell nicht enthält, würde ich vorschlagen, ein Problem im CoreCLR-Repo zu öffnen

@ steveL-msft das ist das problem, denke ich. Es scheint anderswo gut zu funktionieren, mit einigen Einschränkungen noch. Das Laden der nativen Bibliothek funktioniert beispielsweise, wenn sie sich im selben Ordner wie die Haupt-SkiaSharp.dll befindet, aber für PS funktioniert dies aufgrund der Änderung, die @adityapatwardhan erwähnt, nicht. Dies vervielfacht nur die Schwierigkeit bei der Arbeit mit PowerShell und beim Umgang mit Modulen.

In den meisten Situationen bei der Arbeit mit solchen nativen Bibliotheken kann es abstrahiert werden, da Sie es ohnehin nur für jede unterstützte Plattform verpacken können. Wir haben diesen Luxus für PS-Module ganz einfach nicht, es sei denn, ich möchte drei oder mehr vollständig separate Galeriemodule verpacken und warten, was leider sehr schlecht für die Auffindbarkeit und die Endbenutzererfahrung ist.

Angesichts der Tatsache, dass PS ein plattformübergreifendes Tool ist, benötigen wir eine Möglichkeit, native Bibliotheken in Modulen zu verwalten, oder wir müssen das PackageManagement-Modul erkennen lassen, dass separate Bibliotheken pro Plattform aus dem nupkg extrahiert werden müssen.

Ich bin offen für alternative Lösungen, aber es scheint, dass das Deck hier ziemlich knapp ist. :verwirrt:

@ vexx32 Ich denke, Sie möchten so etwas https://github.com/PowerShell/PowerShellGet/issues/273

In CoreFX 3.0 erhalten wir einige neue APIs für native Pinoke-Bibliotheken. Sie sind erst am Anfang der Straße, aber sie wollen etwas noch besseres als Mono machen.

Derzeit meine Lösung Problemumgehung wird wie folgt aussehen:

  1. Native Bibliotheken für Mac und Linux befinden sich im Modulstamm, da dies der einzige Ort zu sein scheint, an dem ich sie ablegen und im Moment zuverlässig laden kann.
  2. Ein ScriptsToProcess behandelt p / invoke beim Modulimport, um entweder die 32- oder 64-Bit-Version der nativen Windows-Bibliothek hinzuzufügen, die in Unterordnern gespeichert werden muss, da die Dateinamen leider identisch sind.

Nicht ideal, und wie @TylerLeonhardt heute aus seinem Stream

@ vexx32 kennt die Details nicht. Wenn Sie in C # einen Repro erstellen können, der PowerShell nicht enthält, würde ich vorschlagen, ein Problem im CoreCLR-Repo zu öffnen

Nein: In C # ist das kein Problem

In C # Sie entweder:

  1. Entwickeln Sie eine App und geben Sie separate Installationsprogramme pro Plattform frei. Sie müssen sich nur um einen Bibliothekssuchpfad kümmern.
  2. Entwickeln Sie eine Bibliothek und geben Sie _NuGet-Pakete_ frei. Nuget gibt Ihnen ein Pfadmuster, das Ihnen sagt, wo Sie Ihre Bibliotheken ablegen sollen. Dann kümmert sich studio / msbuild um alles für Sie.

@ vexx32 Ich denke, Sie möchten so etwas wie PowerShell / PowerShellGet # 273

Nein: Dies ist kein plattformübergreifendes Problem

Deshalb wurde dieser Fehler behoben. Seitdem PowerShell 6.2 @ vexx32 nicht einmal eine Version erhalten kann, die nur in Windows funktioniert, kann er nicht herausfinden, wie PowerShell den richtigen Bibliothekssuchpfad verwendet.

Zumindest in Windows ist ap / invoke verfügbar, mit dem ich herumspielen kann. Das ist eine Problemumgehung, aber wir brauchen wirklich eine bessere Lösung.

Auf Unix-Systemen ist jedoch kein solches p / invoke_ verfügbar, was ich finden konnte, und aus welchem ​​Grund auch immer, PowerShell fragt nicht die Umgebungsvariablen ab, die normalerweise angeben, von welchen Pfaden gesucht werden soll, wenn Sie versuchen, DllImport zu verwenden eine von PS geladene Baugruppe.

Auf praktisch allen Plattformen ist dies also ... äußerst unfreundlich, um irgendetwas damit zu tun. 😅

Gegenwärtig scheint es keine andere Option zu geben, als alle nativen Unix-Assemblys direkt in den Modulstammordner aufzunehmen (was die von Ihnen unterstützten Plattformen einschränkt, da es häufig zu Namenskollisionen kommen kann) und dann vorsichtig zu sein, welche Ordner, den Sie mit p / invoke zu den Suchpfaden unter Windows hinzufügen (wenn Sie x86 Windows unterstützen @TylerLeonhardt sagt mir, dass Azure-Funktionen aus irgendeinem seltsamen Grund auf x86 ausgeführt werden.

Dies ist, was ich in meiner psm1-Datei verwende, um die entsprechende native Binärdatei für die Plattform vorzuladen. Hinweis: Ich habe dies nur unter Linux und Windows gezielt getestet:

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

Und dies ist der C # -Code, der in Acme.Api.dll enthalten ist

[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 Sie könnten ein Beratungsproblem in CoreCLR Repo eröffnen. Ich denke, sie haben das zu sagen.

@rkeithhill Dies funktioniert nur für .NET-Binärdateien. Ich muss eine .NET-Binärdatei laden, die selbst P / invoke verwenden muss, um eine native Assembly zu laden. Dort versagt es und fällt komplett auseinander. Ich habe keine Kontrolle über diese Bibliothek, es handelt sich um eine Bibliothek eines Drittanbieters (in diesem Fall SkiaSharp). Ich kann diese Bibliothek nicht in mein Binärmodul laden, da sie gleichzeitig mit dem Laden von Code aus meinem Modul geladen werden muss, da mein Modul von Typen abhängig ist, die nur mit geladenem SkiaSharp vorhanden und funktionsfähig sind.

@isazonov Wie bereits einige Male in dieser Ausgabe behandelt, ist dies in .net Core kein Problem, da Sie für die meisten Anwendungen einfach separate Pakete pro Plattform generieren können.

PowerShell hat keine Möglichkeit, dies zu tun, und entfernt im Übrigen einige Standardpfade aus den Bibliothekssuchpfaden, sodass es in einigen Kontexten buchstäblich unmöglich ist, mit nativen Assemblys zu arbeiten, und in denen, die gerade vorkommen, sehr fragil und besonders ist Arbeit.

@ vexx32

Ich muss eine .NET-Binärdatei laden, die selbst P / invoke verwenden muss, um eine native Assembly zu laden. Hier versagt in und fällt komplett auseinander.

Genau das macht dieser Code. Ich benutze es seit einiger Zeit erfolgreich unter 6.2 unter Windows und Linux.

Geändert, sorry, ich war nicht klar. Ich kann dies nicht in meiner eigenen Binärdatei tun. Die Typen müssen vorhanden sein, wenn mein eigenes Modul geladen wird. Ich kann es während der Befehlsinstanziierung nicht importieren lassen ... und ich sollte es nicht müssen.

PowerShell sollte beim Laden von Binärdateien nicht weniger funktionsfähig sein als .net Core.

Trotzdem werde ich sehen, ob ich einige Dinge anpassen und meine Problemumgehungen verbessern kann. Schätzen Sie die Beispiele!

Aber wirklich ... ich verstehe nicht, warum ich mich so viel Mühe geben sollte, um das zu umgehen. Es sollte einfach funktionieren und bis PowerShell involviert ist ... Es funktioniert.

Einverstanden, dass die Reifen ein Schmerz sind. Ich habe nur versucht zu zeigen, wie das Laden der nativen Bibliothek über p / invoke unter Windows und Linux erfolgen kann. :-)

@ vexx32 Ich sehe Spaltung in der Diskussion. Könnten Sie klarstellen, dass dies ein Problem ist? Sie haben ein Modul mit nativen Bibliotheken und müssen die richtige Bibliothek mit Abhängigkeiten von einer bestimmten Plattform laden?

Art von. Dies ist das Layout, das ich derzeit habe, das aber zum Teufel chaotisch ist. Alles, wofür ich keinen Pfad für via p / invoke definieren kann, muss sich im Modulstamm befinden (was auf einigen Plattformen auf Probleme mit Namenskollisionen stößt).

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

SkiaSharp.dll ist .NET und eine Assembly, auf die libSkiaSharp- Dateien sind native DLLs pro Plattform. Ich kontrolliere den Code in SkiaSharp.dll nicht, daher kann ich diese DLL nicht zwingen, die Pfade zu aktualisieren, bevor sie p / aufruft.

Also ... ich habe ein Modul mit einer .NET-Abhängigkeit. Diese Abhängigkeit hängt von einer nativen Bibliothek ab, die mit DllImport geladen wird. Die genaue Datei ist je nach Plattform / Architektur unterschiedlich (aber einige der Dateien weisen Namenskollisionen auf, sodass die Plattformen, die ich tatsächlich unterstützen kann, eingeschränkt werden).

Mit dem Code von @rkeithhill kann ich möglicherweise auch die native Linux-Bibliothek in einen Unterordner verschieben. Ich bin mir bei MacOS nicht sicher, es gibt wahrscheinlich etwas Ähnliches, aber ich habe noch keine Dokumentation zu Methoden dafür gefunden.

Idealerweise sollten sich ALLE diese Dateien im schlimmsten Fall in einem Unterordner befinden, etwa /PSWordCloud/runtime/<platform>/ - leider aufgrund der Tatsache, dass PowerShell die Pfadladespezifikationen gegenüber den Standardeinstellungen geändert hat, nur Dateien im Stammverzeichnis Ordner des Moduls kann über DllImport geladen werden. Andere Speicherorte funktionieren möglicherweise, aber (zumindest unter Mac / Linux) _all_ der Umgebungsvariablen auf den Plattformen, die native Laufzeitladepfade definieren sollen, werden von PowerShell vollständig ignoriert.

Ich glaube, dass $env:Path immer noch funktioniert (obwohl es auf Unix-Plattformen nicht verwendet werden sollte, und ich habe das seit der Version 6.2 nicht mehr getestet, so dass es auch kaputt sein könnte), aber ehrlich gesagt nicht Ich denke, es ist überhaupt eine gute Idee, dieser Variablen eine Menge Pfade hinzuzufügen. Dies führt zu einer wirklich schrecklichen Benutzererfahrung, wenn der Benutzer diese Umgebungsvariable überhaupt verwenden muss. Sie laden ein Modul und plötzlich enthält Ihre Pfadvariable zusätzliche unerwartete Daten, die wahrscheinlich nicht vorhanden sein sollten, aber vorhanden sein müssen, damit die Dinge korrekt funktionieren.

Ich denke, das sollte kein Problem sein. PowerShell sollte über eine robustere Methode zur Verarbeitung nativer Bibliotheksladepfade verfügen, wenn die vordefinierten Methoden von .NET Core überschrieben werden sollen. Ich würde vorschlagen, eine Möglichkeit zu haben, einen nativen Bibliotheksladepfad im Modulmanifest anzugeben oder etwas in dieser Richtung, eine Möglichkeit zu definieren, wo pro Plattform gesucht werden soll, wenn etwas nativen Code aufrufen muss.

Ich habe das gleiche Problem und der Bericht von @ vexx32 enthält alle Elemente in der Kette.
Ich bin damit einverstanden, dass PowerShell eine robustere Methode für den Umgang mit nativen Bibliotheken haben sollte.

Ich denke nicht, dass es überhaupt eine gute Idee ist, dieser Variablen eine Menge Pfade hinzuzufügen

Einverstanden und ebenso bin ich nicht daran interessiert, SetDllDirectory da dies auch (pwsh) prozessweit ist. Das Modulmanifest könnte aktualisiert werden, damit Modulautoren eine Liste nativer DLLs pro OS-Arch-Kombination bereitstellen können. Anschließend lädt pwsh diese nativen DLLs vor, bevor Assemblys vorgeladen werden. Das heißt, wenn jemand Ihre Assembly aus einer anderen Sprache verwenden möchte, sollte die Assembly wirklich ihre eigenen nativen DLLs vorladen. Der Benutzer der Assembly sollte diese IMO nicht ausführen müssen.

@ vexx32 Ich "denke", dass dlopen unter macOS funktioniert.

Ich werde es versuchen, wenn ich kann (Mac-VMs sind ein bisschen ... komisch, heh), aber aus dem Speicher verwendet Mac keine .so -Dateien, also müsste ich es definitiv versuchen - - Die meisten nativen Bibliotheken, auf die ich auf Macs verweise, scheinen .dylib .

Die Assembly führt hier ihre eigene Vorladung durch (zumindest bin ich mir ziemlich sicher, dass dies der Fall ist ...). Ich muss nur alles als Sonderfall festlegen, da die Assembly nicht weiß, wo sie mit der Behandlung von Pfaden durch PS suchen soll. : /

Ich denke, dass macOS .dylib sodass Sie diese Assembly dreimal mit verschiedenen definierten Makros kompilieren müssen, z.

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      

Ich wünschte wirklich, ich müsste dies nicht für jede Plattform neu kompilieren, da dies eine netstandard2-Assembly ist. AFAICT Der für das Attribut DllImport angegebene lib-Namenswert muss jedoch eine Kompilierungszeitkonstante sein. :-(

Ja. Da es sich um ein PowerShell-Modul handelt, würde ich am Ende eine dynamische On-Import-Kompilierung mit Add-Type -TypeDefinition $srcString mit einer Plattformprüfung durchführen und dann auf das richtige Verzeichnis verweisen. Zum Glück können Sie dafür ScriptsToProcess in einem Modulmanifest verwenden.

Ist libdl.so eine gültige und verwendbare Bibliothek, um dies unter MacOS zu tun?

Aber ja, wir brauchen eine bessere Lösung. 😄

@ vexx32 Hast du Mono MapDll gesucht? Die Funktion ermöglicht die automatische Zuordnung zu einer geeigneten nativen Bibliothek basierend auf den Bedingungen (Bogen, Plattform usw.). Ich würde erwarten, dass es das ist, was du brauchst. Möglicherweise sollte SkiaSharp.dll neu kompiliert werden, aber es kann in einer externen Konfigurationsdatei konfiguriert werden. Ähnliches wird in Core implementiert.

Wenn ich mich richtig erinnere, verwendet .NET Core unter Windows kein Mono, daher wäre es wiederum nur eine Teillösung.

Ich schätze alle hier angebotenen Optionen sehr und werde meine aktuellen Optionen gründlich untersuchen. Letztendlich muss PowerShell jedoch über etwas verfügen, das plattformübergreifende Module für diesen Zweck in Zukunft verwenden können . 🙂

Wenn ich mich richtig erinnere, verwendet .NET Core unter Windows kein Mono, daher wäre es wiederum nur eine Teillösung.

Ich sage über neue Funktionen. Sehen
Dllmap-Designdokument https://github.com/dotnet/coreclr/blob/c3e1bd5ccc482c9a7670762676bda95ebd34707d/Documentation/design-docs/dllmap.md
und frühere Version des Dokuments
https://github.com/dotnet/coreclr/blob/6a48f05c77e80b604d71a9b84cc6b014306e849e/Documentation/design-docs/dllmap.md#

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

@ Rkeithhill

Ich wünschte wirklich, ich müsste dies nicht für jede Plattform neu kompilieren, da dies eine netstandard2-Assembly ist. AFAICT Der für das DllImport-Attribut angegebene lib-Namenswert muss jedoch eine Kompilierungszeitkonstante sein. :-(

FWIW können Sie die Erweiterung für DllImport weglassen. Wir tun dies für die native Abhängigkeit von PSES, die nicht von Windows

Sie können wahrscheinlich auch die Compiler-Direktiven für die Aufrufe LoadLibrary / dlopen überspringen, indem Sie sie in separaten Schnittstellenimplementierungen speichern. Solange die Methode nicht JIT-fähig ist, sollte sie zur Laufzeit nicht ausgelöst werden.

Ich habe eine mögliche Lösung für dieses Problem gefunden. Ich würde es allerdings ausprobieren müssen. Ich bin gerade damit beschäftigt, an einigen Veröffentlichungen zu arbeiten, also werde ich höchstwahrscheinlich Anfang nächster Woche damit anfangen. Ich werde dieses Problem mit meinen Ergebnissen aktualisieren.

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 Das sieht aus wie ein plattformübergreifender Wrapper um LoadLibrary / dlopen. Schöner Fund!

@adityapatwardhan Ich habe dieses Problem bei LoadUnmanagedDll gefunden. Vielleicht irrelevant für unseren Fall.

Wir sollten in Betracht ziehen, einen Rückruf hinzuzufügen, mit dem Benutzer benutzerdefinierte Auflösungsrichtlinien für nicht verwaltete Bibliotheken bereitstellen können. Wir haben vor, dies mit AssemblyLoadContext.LoadUnmanagedDll zu tun. Am Ende wurde nur ein kleiner Teil des Problems behoben, da es für den Standardladekontext nicht funktioniert. Als wir AssemblyLoadContext.LoadUnmanagedDll ursprünglich eingeführt haben, haben wir auch das Überschreiben des Standardladekontexts zugelassen, diese Funktion dann jedoch entfernt und die Auswirkungen auf alle Szenarien nicht berücksichtigt.

Quelle: LoadUnmanagedDllFromPath / LoadUnmanagedDll funktioniert nicht unter Linux, wenn die .so-Datei in einem anderen Pfad als dem Anwendungsausgabeverzeichnis vorhanden ist

Ich denke, mein Link oben auf NativeLibrary-APIs bietet uns eine plattformübergreifende Lösung (nach dem Wechsel zu .Net Core 3.0).

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

Es ist für Plug-In-Modelle. Dies könnte das Problem # 6724, # 6426 und einige andere beheben.
/ cc @ daxian-dbw

@adityapatwardhan irgendein Update seit letztem Monat? Wir möchten hierzu die richtige Anleitung für Dokumente veröffentlichen

Ich konnte mich nicht darauf konzentrieren und bin derzeit mit anderen Elementen mit hoher Priorität beschäftigt. Ich werde irgendwann nächste Woche aktualisieren.

@adamdriscoll kann möglicherweise einige Ratschläge geben, da er wahrscheinlich einige kreative Dinge in UniversalDashboard und AvaloniaUI + PowerShell tun musste.

FWIW Der Code, den ich oben vorgestellt habe, wird für ein internes Modul verwendet, das eine native Bibliothek verwendet. Es läuft ohne Probleme unter Windows und Linux. Ich habe es nicht unter macOS getestet, aber abgesehen von Änderungen am Pinvoke dlopen und dem Ändern des Namens der freigegebenen Bibliothek unter Mac sollte es funktionieren. Dies ist auf .NET Core 2.2.

UD macht so ziemlich das Gleiche wie die Lösung von @rkeithhill . Dies funktioniert unter MacOSX sowie unter Linux und Windows. https://github.com/ironmansoftware/universal-dashboard/blob/master/src/UniversalDashboard/Server/CustomAssemblyLoadContext.cs

Das Problem ist, dass es viel mehr Variationen der Ordnerpfade geben kann, um die verschiedenen Binärdateien zu laden, so dass es ein bisschen kluge ist. Wenn ein neuer Binärtyp basierend auf einer neuen Abhängigkeit ausgegeben wird, wird möglicherweise ein neuer Ordner angezeigt und UD lädt die Assembly nicht.

@rkeithhill führen Sie alle WindowsLinux-Binärdateien in einem einzigen Ordner zusammen? (BEARBEITEN: Eigentlich verstehe ich das. Sie haben eine Wrapper-DLL-Assembly, die nur die native Zielassembly lädt. Tun Sie dies für alle Ihre nativen Assemblys?)

UD behält nur den Veröffentlichungslaufzeitordner bei, als er mit dotnet Publish erstellt wurde, und dann versucht die referenzierte Klasse, alle nativen Binärdateien basierend auf der richtigen Plattformarchitektur zu laden.

Es funktioniert, aber ich wünschte, PS könnte sich nur einen Laufzeitordner ansehen und die richtigen Binärdateien laden. Scheint, als müsste dotnet das tun, wenn es startet, also gibt es vielleicht einen Code, den man dort schnappen kann.

Ich ändere, wo ich nach nativen Assemblys suche, basierend auf dem Ergebnis RuntimeInformation.IsOSPlatform(OSPlatform.Windows) . Wenn dies true zurückgibt, lade ich aus dem Ordner Windows des Verzeichnisses, in dem sich die C # -Baugruppe befindet. Andernfalls lade ich aus dem Ordner Linux .

Tun Sie dies für alle Ihre nativen Assemblys?)

Ja, aber wir haben nur die eine und sie hängt von nichts anderem als den Systembibliotheken (msvcrt usw.) ab.

Können wir eine Standarddateistruktur haben (möglicherweise basierend auf Laufzeitnamen)? Dies würde es uns ermöglichen, den nativen DLL-Resolver zu verbessern.

UD behält nur den Veröffentlichungslaufzeitordner bei, als er mit dotnet Publish erstellt wurde, und dann versucht die referenzierte Klasse, alle nativen Binärdateien basierend auf der richtigen Plattformarchitektur zu laden.

Wenn ich das richtig verstehe, vermeiden Sie die Verwendung von PowerShell zum Laden der nativen Binärdateien, indem Sie .NET sie nur unter die Haube laden, wenn Sie eine verwaltete Binärdatei laden, die von den RID-spezifischen nativen Binärdateien abhängt.

Es funktioniert, aber ich wünschte, PS könnte sich nur einen Laufzeitordner ansehen und die richtigen Binärdateien laden. Scheint, als müsste dotnet das tun, wenn es startet, also gibt es vielleicht einen Code, den man dort schnappen kann.

Ja, ich vermute, dass in der .NET-Laufzeit selbst ein gewisses RID-Bewusstsein auftritt. Ich habe versucht zu vermeiden, dass wir diesen Weg gehen müssen, aber es klingt so, als müssten wir es irgendwann tun.

@joeyaiello

Wenn ich das richtig verstehe, vermeiden Sie die Verwendung von PowerShell zum Laden der nativen Binärdateien, indem Sie .NET sie nur unter die Haube laden, wenn Sie eine verwaltete Binärdatei laden, die von den RID-spezifischen nativen Binärdateien abhängt.

Ja genau. UD lädt einfach alles für die Ziel-RID. Es ist also nicht wirklich klug, was die tatsächlichen Abhängigkeiten sind. Ich bin mir nicht sicher, ob es einen guten Weg gibt, das überhaupt zu erkennen.

@ vexx32 Ich habe PSWordCloud installiert. Ist die Ordnerstruktur allgemein? Wenn ja, können wir unseren Resolver intelligenter machen. Für den Fall, dass ich Sie fragen möchte - könnten Sie bitte neue Pester-Tests vorbereiten, die auf vereinfachter PSWordCloud basieren (es reicht aus, wenn wir etwas Einfaches aus den nativen Bibliotheken wie "Hello World" holen)? Wir würden das als ausstehend zusammenführen und es wird den Weg für die Arbeit an unserem Resolver ebnen. Vielen Dank!

Derzeit ist die Ordnerstruktur von PSWordCloud nicht allgemein, nein. Das kann natürlich geändert werden.

Ja, dafür kann ich absolut einen einfachen Prüfstand zusammenstellen. Pester ist etwas knifflig, nur basierend auf der Art der Ausgabe, nehme ich an, aber ich denke, wenn es fehlschlägt, erzeugt es eine Ausnahme, die Sie abfangen können, obwohl diese Ausnahme vom Typinitialisierer ausgelöst wird, wenn ich mich richtig erinnere.

Wäre es angemessen, wenn der Test selbst die Skia-DLLs über Dotnet abruft, oder wäre es besser, sie im Repo zu verpacken? Ich denke, wenn wir sie jedes Mal ziehen, ist es keine so große Sache, wenn sie als Funktionstest gekennzeichnet werden, sobald sie nicht mehr anstehen? :Denken:

Wenn Sie einen Testknoten mit indirekter Abhängigkeit von Skia-DLLs implementieren könnten, wäre es schön. Und ja, wir könnten Skia von Nuget installieren. Obwohl der Test zuverlässiger wäre, wenn wir die DLLs mit dem Testmodul direkt in ~ unserem Testmodulordner ~ einem Assert-Ordner ablegen würden.
Es scheint, dass Nuget die beste Vorgehensweise darin besteht, native DLLs in RID-benannten Ordnern abzulegen. Ja?

Ja, das kann ich! Ich werde es mir morgen ansehen. :erröten:

Ich habe meinen Kommentar aktualisiert - bitte legen Sie das Testmodul in einen Test Assert-Ordner.

@rjmholt Ich habe einige Versuche unternommen, den von Ihnen angegebenen Code zu verwenden, damit er für PSWordCloud funktioniert, aber er funktioniert immer noch überhaupt nicht. Der Ladevorgang wird erfolgreich abgeschlossen, aber das Laden der verwalteten SkiaSharp.dll, nachdem die nicht verwaltete Bibliothek die geladene DLL auf Unix-Plattformen immer noch nicht verwendet. Es schafft es jedoch irgendwie, für Windows zu funktionieren.

Siehe meinen WIP-Zweig hier https://github.com/vexx32/PSWordCloud/tree/FixPInvokes - führen Sie build.ps1 , um das Modul zu erstellen und alle Dateien an den richtigen Stellen abzulegen. Es wird das Modul auch für Sie importieren. Das Aufrufen von New-WordCloud nach dem Ausführen des Builds funktioniert unter Windows, schlägt jedoch unter Mac OS vollständig fehl.

@SeeminglyScience siehst du irgendetwas, was ich dort falsch gemacht habe? : /

GitHub
Erstellen Sie mit PowerShell hübsche Wortwolken! Tragen Sie zur Entwicklung von vexx32 / PSWordCloud bei, indem Sie ein Konto auf GitHub erstellen.

@ SteveL-MSFT Was ist der Plan für den Umgang mit nativen Bibliotheken derzeit? 😕

: tada: Dieses Problem wurde in # 11032 behoben, das nun erfolgreich als v7.0.0-rc.1 .: tada:

Praktische Links:

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen