Powershell: Загрузка собственных библиотек не работает

Созданный на 27 апр. 2019  ·  70Комментарии  ·  Источник: PowerShell/PowerShell

При попытке загрузить модуль, который требует p / invoke из сторонней собственной библиотеки, не удается загрузить библиотеку из той же папки, что и DLL, выполняющая p / invoking.

Это _используется_ для работы_ в PowerShell Core 6.1.0, но регрессирует между 6.2.0-preview1 и 6.2.0-preview2. Он по-прежнему работает в Windows PowerShell.

Собственная библиотека, которую он не может найти, находится в той же папке, что и SkiaSharp.dll, который пытается ее найти. См. Https://github.com/PowerShell/PowerShell/issues/8861#issuecomment -462362391 и эту ветку выпуска, чтобы узнать, почему это было сделано таким образом.

Действия по воспроизведению

Install-Module PSWordCloud
New-WordCloud

Ожидаемое поведение

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

Фактическое поведение

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

Запрос базового исключения с помощью $Error[0].Exception.GetBaseException() | Format-List * -F дает следующее:

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

Данные окружающей среды

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

_Примечание: _ То же самое было протестировано в среде Mac (спасибо @steviecoaster!) И тоже не удалось. Я подозреваю, что он будет таким же и в дистрибутивах Linux.

/ cc @ SteveL-MSFT

Issue-Question Resolution-Fixed

Самый полезный комментарий

@rkeithhill

Мне очень жаль, что мне не пришлось перекомпилировать это для каждой платформы, потому что это сборка netstandard2. Но AFAICT значение имени библиотеки, предоставленное атрибуту DllImport, должно быть константой времени компиляции. :-(

FWIW вы можете опустить расширение для DllImport . Мы делаем это для собственной зависимости PSES, отличной от Windows. Не помогает, если базовые имена не совпадают.

Вы также, вероятно, можете пропустить директивы компилятора для вызовов LoadLibrary / dlopen , сохранив их в отдельных реализациях интерфейса. Пока метод не JIT-JIT, он не должен бросать во время выполнения.

Все 70 Комментарий

@adityapatwardhan, можешь взглянуть на это?

Вы можете посмотреть использованные пути с помощью утилиты procmon а затем попытаться исправить с помощью HintPath

Я посмотрю на это, но, учитывая, что это не та библиотека, которую я загружаю напрямую (она загружается самим основным SkiaSharp.dll), я не уверен, что это слишком много. Тот факт, что есть четыре возможных файла библиотеки для загрузки в зависимости от платформы, также усложняет ситуацию.

Но, тем не менее, это не похоже на преднамеренное изменение, поэтому, вероятно, следует обратить внимание на регресс.

Команда MSFT усердно работает над vNext p / invokes в .Net Core 3.0 и, возможно, они что-то перенесли на 2.1. Я не помню, чтобы мы что-то меняли в загрузке dll, связанной с PowerShell.

В 6.2.0-preview2 были # 8073 и несколько изменений с пометкой Internal . Я понятия не имею, что еще могло измениться во время этого. 😕

Изменение PR заключается в загрузке dll модуля сначала из папки модуля, а затем из GAC. Ваша проблема в том, что модуль dll ссылается на вторую dll и автоматическая загрузка dll (это Core, а не PowerShell) не обнаруживает dll. Мое предложение - явно добавить путь к dll в файле проекта.

@iSazonov Я добавил несколько ссылок на HintPath в свой csproj, но все, что я получаю, - это предупреждение, когда я dotnet publish . После загрузки в поведении модуля не происходит никаких изменений.

После небольшого чтения, похоже, что HintPath используется для ссылки на внешние сборки, которые _не включены_ в саму сборку проекта. Сборки SkiaSharp включены, поэтому добавление HintPath для компиляции / сборки не влияет на библиотеки DLL Skia или пути, которые они просматривают во время выполнения.

Для справки это мой 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>

Я добавил следующие строки, чтобы не повлиять (кроме предупреждения о времени сборки / публикации, что они не могут быть найдены, потому что, ну, они не существуют до тех пор, пока публикация не будет завершена):

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

Из того, что я читал на HintPath, похоже, это не влияет на то, какие пути ищутся при загрузке DLL, или эта DLL пытается загрузить другие библиотеки DLL для целей p / invoke.

_That_ HintPath используется только во время компиляции.

То, как вы это говорите, звучит так, будто я пропустил еще одну?

@ vexx32 Если у вас простой код репо, вы можете спросить его в репозитории CoreCLR.

Я не уверен, смогу ли я сейчас исключить изменение, специфичное для Powershell. Возможно, мне придется провести небольшой тест для консольного приложения и посмотреть, как это пойдет.

Благодаря совету @Jaykul мне удалось создать

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)

Однако тот факт, что для выполнения этой работы _требуется_ дополнительный p / invoke, выглядит нелепо. Я также очень смущен тем, что изменило и удалило собственный путь к каталогу загруженного SkiaSharp.dll из стандартных местоположений поиска, как описано здесь .

@ vexx32 Мне не удалось воспроизвести проблему. Я что-то пропустил в репро? Может конкретная версия если 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

Извините, как уже упоминалось, я исправил это в 2.1.1.

Если вы установите 2.1.0 специально, вы увидите ошибку, так как не хватает описанного выше обходного пути p / invoke.

Хорошо, теперь я могу воспроизвести. Я взгляну.

@ vexx32 Кажется, мы загружаем весь файл, указанный в FileList из psd1 . Вам это нужно? Когда я удалил все записи из FileList я больше не могу воспроизвести проблему.

Это так? Это очень странно. Нет, мне это особо не нужно, и, вероятно, я все равно удалю его, так как не хочу постоянно его обновлять, но это было в нескольких версиях.

Кажется странным, что это внезапно повлияло на загрузку DLL.

Кажется, что мы загружаем все файлы, перечисленные в FileList файла psd1.

У нас есть проблема с поддержкой if в файлах .psd1. Таким образом, мы могли загружать библиотеки DLL для конкретной платформы.

@iSazonov это # 5541 . Я скоро дам вам обновления.

Это возможность. Мне интересно, _now_, достаточно ли здесь простого добавления пути к собственным библиотекам (всем из них) в FileList ... Мне может даже не понадобиться p / invoke. Я проведу небольшое тестирование! 😊

РЕДАКТИРОВАТЬ: Нет, я не вижу, чтобы это имело какое-либо влияние. :(

Мы вызываем Assembly.Load() который загружает только управляемые сборки.

Мы внесли изменение в 6.2.0-preview.2, где начали загружать библиотеки DLL из папки модуля перед поиском в GAC. Побочным эффектом этого изменения является то, что мы также загружаем библиотеки DLL в FileList.

В этом есть смысл. Есть ли у нас открытая проблема с документами? Похоже, что такое изменение будет очень сложно отследить, если кто-то столкнется с ним, а мы не задокументировали его. Вероятно, нам следует задокументировать, что по этой причине вы не хотите помещать собственные сборки в список файлов.

Цените помощь в отслеживании! На данный момент решение P / invoke, о котором я упомянул, дает мне наибольшую гибкость, но было бы разумно добавить обработку таких вещей при загрузке модуля.

Например, допустим, в PrivateData мы добавляем запись с именем RuntimeLibPaths, которая представляет собой хэш-таблицу, в которой ключи соответствуют именам / архитектурам поддерживаемых платформ, а значения представляют собой путь к папке, содержащей собственную библиотеку. Соответствующие папки добавляются к путям поиска dll при импорте с помощью PowerShell внутри, а не должны добавляться самим модулем при импорте.

Есть ли у нас открытая проблема с документами?

У нас нет.

@adityapatwardhan @ SteveL-MSFT спасибо @TylerLeonhardt (непреднамеренно), напомнив мне, что это должно работать и с вещами, отличными от Windows, если я хочу быть внимательным ... Мне нужно более полное решение.

В настоящее время, похоже, нет какой-либо четкой документации о том, как внутреннее взаимодействие должно обрабатываться в самом .NET Core. Все, что я могу найти, - это страница .NET Framework в документации MS.

Это обрабатывается через Mono или что-то в этом роде? Если это через Mono, глядя на эту страницу, на самом деле есть _bug в Mono_ в Mac OS, который не позволяет должным образом соблюдать пути загрузки библиотеки ...

PowerShell ... должен с этим справиться. Я не уверен, что есть способ справиться с этим должным образом на всех платформах из самого модуля.

@ vexx32 не знает подробностей, если вы можете создать репродукцию на C #, которая не включает PowerShell, я бы предложил открыть проблему в репозитории CoreCLR

@ steveL-msft, я думаю, в этом проблема. Кажется, что в другом месте он работает нормально, но с некоторыми оговорками. Например, загрузка собственной библиотеки работает, если она находится в той же папке, что и основной SkiaSharp.dll, но для PS это не работает из-за изменения, упомянутого в @adityapatwardhan . Это лишь увеличивает сложность работы с PowerShell и того, как он обрабатывает модули.

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

Учитывая, что PS является кроссплатформенным инструментом, нам нужен какой-то способ обработки собственных библиотек в модулях, или нам нужен какой-то способ, чтобы модуль PackageManagement распознал, что должны быть отдельные библиотеки, извлеченные для каждой платформы из nupkg.

Я открыт для альтернативных решений, но, похоже, колода здесь довольно скудная. :смущенный:

@ vexx32 Я думаю, вам нужно что-то вроде этого https://github.com/PowerShell/PowerShellGet/issues/273

В CoreFX 3.0 мы получаем несколько новых API для нативных библиотек pinoke. Они только в начале пути, но хотят сделать что-то даже лучше, чем Mono.

В настоящее время обходной путь моего решения выглядит так:

  1. Собственные библиотеки для Mac и Linux находятся в корне модуля, так как это, кажется, единственное место, где я могу их разместить и надежно загрузить на данный момент.
  2. ScriptsToProcess обрабатывает p / invoke при импорте модуля для добавления 32-разрядной или 64-разрядной версии собственной библиотеки Windows, которая должна храниться во вложенных папках, поскольку имена файлов, к сожалению, идентичны.

Не идеально, и, как @TylerLeonhardt сегодня может подтвердить из своего потока, это все еще по какой-то причине не работает при работе в Функциях Azure в настоящий момент.

@ vexx32 не знает подробностей, если вы можете создать репродукцию на C #, которая не включает PowerShell, я бы предложил открыть проблему в репозитории CoreCLR

Нет: в C # это не проблема

В C # вы либо:

  1. Разработайте приложение и выпустите _отдельные установщики_ для каждой платформы и оставьте только _один путь поиска библиотеки_, о котором нужно беспокоиться.
  2. Разработайте библиотеку и выпустите _NuGet packages_, а nuget предоставит вам шаблон пути, сообщающий вам, где разместить ваши библиотеки, а затем studio / msbuild позаботится обо всем за вас.

@ vexx32 Я думаю, вам нужно что-то вроде этого PowerShell / PowerShellGet # 273

Нет: это не кроссплатформенная проблема

Вот почему была зарегистрирована эта ошибка. С тех пор, как PowerShell 6.2 @ vexx32 не может получить даже версию, работающую в _только Windows_, он не может понять, как заставить PowerShell использовать правильный путь поиска библиотеки.

В Windows, по крайней мере, доступен ap / invoke, который позволяет мне возиться с ним. Это обходной путь, но на самом деле нам нужно лучшее исправление.

Однако в системах Unix такой p / invoke_ недоступен, из того, что я смог найти, и по какой-то причине PowerShell не запрашивает переменные среды, которые обычно указывают, какие пути искать, когда вы пытаетесь выполнить DllImport из сборка загруженная PS.

Так что практически на всех платформах это ... крайне недружелюбно с ними делать. 😅

В настоящее время, похоже, нет другого варианта, кроме включения всех без исключения собственных сборок Unix непосредственно в корневую папку модуля (что ограничивает платформы, которые вы можете поддерживать, поскольку часто могут возникать конфликты имен), а затем осторожность с тем, какие папку, которую вы добавляете в пути поиска в Windows с помощью p / invoke (если вы заботитесь о поддержке Windows x86 - и @TylerLeonhardt сообщает мне, что функции Azure работают на x86 по какой-то странной причине, так что это необходимо, если вы хотите работать с те.)

Это то, что я использую в моем файле psm1 для предварительной загрузки соответствующего собственного двоичного кода для платформы. Примечание: я нацелился / тестировал это только на Linux и Windows:

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

И это код C #, который входит в 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 Вы можете открыть вопрос с консультацией в репозитории CoreCLR. Думаю, у них есть что сказать.

@rkeithhill это работает только для двоичных файлов .NET. Мне нужно загрузить двоичный файл .NET, который сам должен использовать P / invoke для загрузки собственной сборки. Вот где он терпит неудачу и полностью разваливается. Я не контролирую эту библиотеку, это сторонняя библиотека (в данном случае SkiaSharp). Я не могу загрузить эту библиотеку в свой двоичный модуль, так как ее необходимо загружать одновременно с загрузкой кода из моего модуля, поскольку мой модуль зависит только от типов, представленных и работающих с загруженным SkiaSharp.

@isazonov, как уже было пару раз освещено в этом выпуске, это не проблема для .net Core, потому что вы можете просто создавать отдельные пакеты для каждой платформы для большинства приложений.

У PowerShell нет возможности сделать это, и он случайно удаляет некоторые стандартные пути из путей поиска библиотеки, что делает буквально невозможным работать с собственными сборками в некоторых контекстах и ​​очень хрупкими и специфичными в тех, которые случаются. Работа.

@ vexx32

Мне нужно загрузить двоичный файл .NET, который сам должен использовать P / invoke для загрузки собственной сборки. Вот где он терпит неудачу и полностью разваливается.

Это именно то, что делает этот код. Уже некоторое время успешно использую его в 6.2 как для Windows, так и для Linux.

Поправлен, извините, я не понял. Я не могу сделать это в моем собственном двоичном файле, типы должны присутствовать при загрузке моего собственного модуля; Я не могу импортировать его во время создания экземпляра команды .... Да и не должен.

PowerShell не должен быть менее функциональным, чем ядро ​​.net, когда дело доходит до загрузки двоичных файлов.

Тем не менее, я посмотрю, смогу ли я адаптировать некоторые вещи и улучшить свои обходные пути. Оцените примеры!

Но на самом деле ... Я не понимаю, почему я должен так долго работать над этим. Он должен просто работать, и пока не подключится PowerShell ... Он работает.

Согласился, что обручи причиняют боль. Просто пытался показать, как можно выполнить загрузку собственной библиотеки через p / invoke в Windows и Linux. :-)

@ vexx32 В обсуждении вижу раскол. Не могли бы вы пояснить, что это проблема - у вас есть модуль с собственными библиотеками, и вам нужно загрузить правильную библиотеку с зависимостями от конкретной платформы?

Вроде, как бы, что-то вроде. Это макет, который у меня сейчас работает, но он чертовски беспорядочный. Все, для чего я не могу определить путь через p / invoke, должно находиться в корне модуля (что на некоторых платформах вызывает проблемы с конфликтом имен).

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

SkiaSharp.dll - это .NET, это сборка, на которую есть ссылка для моей основной dll командлета. Четыре файла libSkiaSharp являются собственными библиотеками DLL для каждой платформы. Я не контролирую код в SkiaSharp.dll, поэтому я не могу заставить эту DLL обновлять пути до того, как она вызовет p /.

Итак ... у меня есть модуль с зависимостью от .NET. Эта зависимость зависит от собственной библиотеки, загружаемой с помощью DllImport; точный файл отличается для каждой платформы / архитектуры (но у некоторых файлов есть коллизии имен, что ограничивает платформы, которые я действительно могу поддерживать).

Используя код @rkeithhill , я думаю, что могу также переместить собственную библиотеку Linux в подпапку. Не уверен в MacOS, вероятно, есть что-то похожее, но мне еще предстоит найти документацию по методам для этого.

_Идеально_, ВСЕ эти файлы должны быть в худшем случае во вложенной папке, что-то вроде /PSWordCloud/runtime/<platform>/ - к сожалению, из-за того, что PowerShell изменил спецификации загрузки пути из стандартных настроек, только файлы в корне Папка модуля может быть загружена через DllImport. Могут работать и другие места, но (по крайней мере, на Mac / Linux) _все_ переменные среды на тех платформах, которые должны определять собственные пути загрузки во время выполнения, полностью игнорируются PowerShell.

Я считаю, что $env:Path все еще работает (хотя он не должен использовать это на платформах Unix, и я не тестировал это с момента выпуска 6.2, так что это тоже может быть сломано), но, честно говоря, я не думаю, что это вообще хорошая идея - добавить к этой переменной кучу путей, это действительно ужасно для пользователя, если ему вообще нужно использовать эту переменную среды. Вы загружаете модуль, и внезапно ваша переменная пути содержит дополнительные неожиданные данные, которых, вероятно, не должно быть, но _з_ должен_ быть там, чтобы все работало правильно.

Я думаю, это не должно быть проблемой; PowerShell должен иметь более надежный метод обработки путей загрузки собственных библиотек, если он будет переопределять предопределенные методы .NET Core. Я бы предложил способ указать путь загрузки собственной библиотеки в манифесте модуля или что-то в этом роде, какой-то способ определить, где он должен искать для каждой платформы, когда что-то нужно p / вызвать некоторый собственный код.

У меня такая же проблема, и отчет @vexx32 содержит все элементы в цепочке.
Я согласен с тем, что PowerShell должен иметь более надежный метод работы с собственной библиотекой.

Я не думаю, что это хорошая идея добавлять к этой переменной кучу путей

Согласен и аналогично, я не хочу использовать SetDllDirectory потому что это тоже (pwsh) процесс. Манифест модуля может быть обновлен, чтобы позволить авторам модулей предоставить список собственных dll для каждой комбинации os-arch, а затем pwsh будет предварительно загружать эти собственные dll перед предварительной загрузкой любых сборок. Тем не менее, если кто-то собирается использовать вашу сборку с другого языка, сборка действительно должна предварительно загрузить свои собственные собственные библиотеки DLL. Пользователь сборки не должен делать это IMO.

@ vexx32 Я "думаю" dlopen работает и на macOS.

Я попробую, когда смогу (виртуальные машины Mac немного ... странные, хех), но по памяти Mac не использует файлы .so поэтому мне определенно придется попробовать - - большинство нативных библиотек, на которые я вижу ссылки на Mac, кажутся .dylib .

Сборка выполняет здесь свою собственную предварительную загрузку (по крайней мере, я почти уверен, что это так ...), мне просто нужно все в особом случае, потому что сборка не знает, где смотреть, как PS обрабатывает пути. : /

Я думаю, что macOS использует .dylib поэтому вам нужно будет трижды скомпилировать эту сборку с определенными разными макросами, например:

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      

Мне очень жаль, что мне не пришлось перекомпилировать это для каждой платформы, потому что это сборка netstandard2. Но, AFAICT, значение имени библиотеки, предоставленное атрибуту DllImport , должно быть постоянной времени компиляции. :-(

Да уж. Поскольку это модуль PowerShell, то в конечном итоге я бы сделал динамическую компиляцию при импорте с Add-Type -TypeDefinition $srcString с проверкой платформы и затем направил ее в правильный каталог. К счастью, вы можете использовать для этого ScriptsToProcess в манифесте модуля.

Кроме того, является ли libdl.so допустимой и пригодной для использования библиотекой для этого в MacOS?

Но да, нам нужно лучшее решение. 😄

@ vexx32 Вы смотрели Mono MapDll? Эта функция позволяет автоматически отображать соответствующую встроенную библиотеку в зависимости от условий (арка, платформа и т. Д.). Я ожидал, что это то, что вам нужно. Возможно, следует перекомпилировать SkiaSharp.dll, но его можно настроить во внешнем файле конфигурации. Нечто подобное будет реализовано в Core.

Если я правильно помню, .NET Core не использует Mono при работе в Windows, поэтому это снова будет лишь частичным решением.

Я очень ценю все варианты, предлагаемые здесь, и я тщательно исследую, какие у меня есть текущие варианты, но, в конце концов, PowerShell необходимо иметь что-то для кроссплатформенных модулей, которые можно было бы использовать для этой цели в будущем. . 🙂

Если я правильно помню, .NET Core не использует Mono при работе в Windows, поэтому это снова будет лишь частичным решением.

Я говорю о новых возможностях. Видеть
Проектный документ Dllmap https://github.com/dotnet/coreclr/blob/c3e1bd5ccc482c9a7670762676bda95ebd34707d/Documentation/design-docs/dllmap.md
и предыдущая версия документа
https://github.com/dotnet/coreclr/blob/6a48f05c77e80b604d71a9b84cc6b014306e849e/Documentation/design-docs/dllmap.md#

Обновление: CurrentContextualReflectionContext
https://github.com/dotnet/designs/blob/master/accepted/runtime-binding.md#rollforward

@rkeithhill

Мне очень жаль, что мне не пришлось перекомпилировать это для каждой платформы, потому что это сборка netstandard2. Но AFAICT значение имени библиотеки, предоставленное атрибуту DllImport, должно быть константой времени компиляции. :-(

FWIW вы можете опустить расширение для DllImport . Мы делаем это для собственной зависимости PSES, отличной от Windows. Не помогает, если базовые имена не совпадают.

Вы также, вероятно, можете пропустить директивы компилятора для вызовов LoadLibrary / dlopen , сохранив их в отдельных реализациях интерфейса. Пока метод не JIT-JIT, он не должен бросать во время выполнения.

Я нашел возможное решение этой проблемы. Хотя я должен попробовать это. В настоящее время я занят работой над некоторыми релизами, так что, скорее всего, займусь этим в начале следующей недели. Я обновлю этот выпуск своими выводами.

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 Это похоже на кроссплатформенную оболочку LoadLibrary / dlopen. Хорошая находка!

@adityapatwardhan Я обнаружил эту проблему вокруг LoadUnmanagedDll. Может, не относится к нашему случаю.

Нам следует подумать о добавлении обратного вызова, который позволит людям предоставлять настраиваемую политику разрешения для неуправляемых библиотек. Мы намеревались сделать это с помощью AssemblyLoadContext.LoadUnmanagedDll. В конце концов, он решил только небольшую часть из них, потому что он не работает для контекста загрузки по умолчанию. Когда мы изначально представили AssemblyLoadContext.LoadUnmanagedDll, мы также разрешили переопределить контекст загрузки по умолчанию, но затем удалили эту возможность и не продумали влияние на все сценарии.

Источник: LoadUnmanagedDllFromPath / LoadUnmanagedDll не работает в Linux, если файл .so присутствует в любом другом пути, кроме каталога вывода приложения

Я думаю, что моя ссылка выше на API NativeLibrary дает нам кроссплатформенное решение (после перехода на .Net Core 3.0).

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

Это для подключаемых моделей. Это может решить проблему, # 6724, # 6426 и некоторые другие.
/ cc @ daxian-dbw

@adityapatwardhan есть ли обновления с прошлого месяца? Мы хотим опубликовать правильное руководство по этому вопросу.

Не могу сосредоточиться на этом, в настоящее время занят другими приоритетными задачами. Я обновлю когда-нибудь на следующей неделе.

@adamdriscoll мог бы дать совет, так как ему, вероятно, пришлось заняться творчеством в UniversalDashboard и AvaloniaUI + PowerShell.

FWIW код, который я представил выше, - это то, что мы используем для внутреннего модуля, который использует собственную библиотеку. Он работает без проблем в Windows и Linux. Не тестировал его на macOS, но кроме настроек pinvoke dlopen и изменения имени общей библиотеки на Mac, он должен работать. Это на .NET Core 2.2.

UD делает почти то же самое, что и решение @rkeithhill . Это работает в MacOSX, а также в Linux и Windows. https://github.com/ironmansoftware/universal-dashboard/blob/master/src/UniversalDashboard/Server/CustomAssemblyLoadContext.cs

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

@rkeithhill , вы объединяете все двоичные файлы WindowsLinux в одну папку? (РЕДАКТИРОВАТЬ: На самом деле, я понял. У вас есть сборка DLL-оболочки, которая просто загружает целевую собственную сборку. Вы делаете это для всех своих собственных сборок?)

UD просто сохраняет папку среды выполнения публикации такой же, как когда она была создана с помощью dotnet publish, а затем указанный класс пытается загрузить все собственные двоичные файлы на основе правильной архитектуры платформы.

Это работает, но я бы хотел, чтобы PS мог просто посмотреть на папку времени выполнения и загрузить правильные двоичные файлы. Похоже, что dotnet должен делать это при запуске, так что, возможно, там есть какой-то код.

Я меняю место поиска нативных сборок в зависимости от результата RuntimeInformation.IsOSPlatform(OSPlatform.Windows) . Если это вернет истину, я загружаю из папки Windows каталога, в котором находится сборка C #. В противном случае я загружаюсь из папки Linux .

Вы это делаете для всех родных сборок?)

Да, но у нас есть только один, и он не зависит ни от чего, кроме системных библиотек (msvcrt и т. Д.).

Можем ли мы иметь некоторую стандартную файловую структуру (возможно, основанную на именах среды выполнения)? Это позволит нам улучшить собственный преобразователь dll.

UD просто сохраняет папку среды выполнения публикации такой же, как когда она была создана с помощью dotnet publish, а затем указанный класс пытается загрузить все собственные двоичные файлы на основе правильной архитектуры платформы.

Итак, если я правильно понимаю, вы избегаете использования PowerShell для загрузки собственных двоичных файлов, просто заставляя .NET загружать их под капотом, когда вы загружаете управляемый двоичный файл, который зависит от собственных двоичных файлов, специфичных для RID?

Это работает, но я бы хотел, чтобы PS мог просто посмотреть на папку времени выполнения и загрузить правильные двоичные файлы. Похоже, что dotnet должен делать это при запуске, так что, возможно, там есть какой-то код.

Да, я подозреваю, что в самой среде выполнения .NET происходит некоторая осведомленность о RID. Я пытался избежать того, чтобы мы пошли по этому пути, но похоже, что в какой-то момент нам может понадобиться это сделать.

@joeyaiello

Итак, если я правильно понимаю, вы избегаете использования PowerShell для загрузки собственных двоичных файлов, просто заставляя .NET загружать их под капотом, когда вы загружаете управляемый двоичный файл, который зависит от собственных двоичных файлов, специфичных для RID?

Да, точно. UD просто загружает все для целевого RID. Так что не совсем разумно, каковы фактические зависимости. Не уверен, есть ли хороший способ даже это обнаружить.

@ vexx32 Я установил PSWordCloud. Общая структура папок? В таком случае мы можем сделать наш резолвер более умным. В случае, если я хочу спросить вас - не могли бы вы подготовить новые тесты Pester на основе упрощенного PSWordCloud (нам достаточно получить что-то простое из собственных библиотек, например «Hello World»)? Мы объединим это как ожидающее, и это откроет путь для работы над нашим резолвером. Благодаря!

В настоящее время структура папок PSWordCloud не является общей, нет. Конечно, это можно изменить.

Да, я могу собрать для этого простой тестовый стенд. Пестер - это немного сложно сделать, исходя только из характера вывода, я полагаю, но я _ думаю_, когда он терпит неудачу, он генерирует исключение, которое вы можете поймать, хотя это исключение выбрасывается из инициализатора типа, если я правильно помню.

Было бы целесообразно, чтобы сам тест извлекал библиотеки DLL Skia через dotnet, или было бы лучше упаковать их в репо? Думаю, если мы будем их извлекать каждый раз, это не так уж важно, если они помечаются как функциональный тест, когда его больше не ожидает? : мышление:

Было бы неплохо, если бы вы могли реализовать тестовый узел с косвенной зависимостью с помощью библиотек Skia. И да, мы могли установить Skia из nuget. Хотя тест был бы более надежным, если бы мы напрямую поместили dll с тестовым модулем в ~ нашу папку тестового модуля ~ в папку assert.
Кажется, что лучшей практикой Nuget является размещение собственных dll в именованных папках RID. Да?

Ага, я могу это сделать! Я посмотрю на это завтра. :румянец:

Я обновил свой комментарий - поместите тестовый модуль в папку test assert.

@rjmholt Я предпринял несколько попыток использовать предоставленный вами код, чтобы заставить его работать для PSWordCloud, но он по-прежнему не работает. Операция загрузки завершается успешно, но загрузка управляемого файла SkiaSharp.dll после того, как неуправляемая библиотека по-прежнему не может использовать загруженную DLL на платформах Unix. Тем не менее, как-то удается работать с Windows.

См. Мою ветку WIP здесь https://github.com/vexx32/PSWordCloud/tree/FixPInvokes - запустите build.ps1 чтобы собрать модуль и разместить все файлы в нужных местах. Он также импортирует модуль для вас. Вызов New-WordCloud после запуска сборки работает в Windows, но полностью не работает в Mac OS.

@SeeminglyScience, ты видишь что-нибудь, что я сделал не так? : /

GitHub
Создавайте красивые облака слов с помощью PowerShell! Участвуйте в разработке vexx32 / PSWordCloud, создав учетную запись на GitHub.

@ SteveL-MSFT каковы планы по работе с собственными библиотеками в настоящее время? 😕

: tada: Эта проблема была устранена в # 11032, который теперь успешно выпущен как v7.0.0-rc.1 .: tada:

Полезные ссылки:

Была ли эта страница полезной?
0 / 5 - 0 рейтинги