[Программирование, .NET] Создание пакета NuGet для библиотеки с платформозависимым API
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Я разрабатываю .NET библиотеку для работы с MIDI файлами и MIDI устройствами – DryWetMIDI. Большинство API библиотеки кроссплатформенное (в рамках поддерживаемых .NET систем, конечно же), однако работа с MIDI устройствами различна на разных операционных системах. На данный момент соответствующий API библиотеки работает только на Windows, однако есть большое желание обеспечить его работу и на других системах. Не буду бросаться в поддержку всего и вся, посему сначала собираюсь поддержать macOS, тем более что данная операционная система не менее популярна для работы с музыкой, а может даже и самая популярная в профессиональных кругах.Разумеется, нет смысла заниматься сразу реализацией реального API, проще проверить всё на маленьком примере. Именно так я и поступил, и хочу пройти путь до работающего решения ещё раз вместе с вами. Краткий список шагов будет приведён в конце статьи.Первые попыткиМоя библиотека, как и положено оной в мире .NET, поставляется в виде NuGet пакета. Поэтому я сразу понял, что в нём нужно будет также поставлять нативные библиотеки, предоставляющие API для конкретной операционной системы.В C# мы можем написать такое определение внешней функции:
[DllImport("test")]
public static extern int Foo();
То бишь нет нужды указывать расширение нативной библиотеки, .NET подставит нужное на основе текущей операционной системы. Иными словами, если рядом с нашим приложением будут лежать файлы test.dll и test.dylib, то на Windows вызовется функция Foo из test.dll, а на macOS – из test.dylib. Можно масштабировать и на *nix, поставляя файл test.so.Чтобы двинуться дальше, создадим проект нашей тестовой библиотеки. В файле .csproj DryWetMIDI указаныTFM netstandard2.0 и net45, поэтому для тестового проекта я также указал эти целевые платформы для приближения к реальным условиям. Проект назовём DualLibClassLibrary, внутри будет всего один файл Class.cs:
using System.Runtime.InteropServices;
namespace DualLibClassLibrary
{
public static class Class
{
[DllImport("test")]
public static extern int Foo();
public static int Bar()
{
return Foo() * 1000;
}
}
}
Кроме того, нам, разумеется, нужны сами нативные сборки (test.dll и test.dylib). Я собрал их из простого кода на C (к слову, такого подхода буду придерживаться затем и в реальной библиотеке):для Windows
int Foo() { return 123; }
для macOS
int Foo() { return 456; }
Если интересно, файлы test.dll и test.dylib создавал в рамках тестового пайплайна в Azure DevOps (в действительности двух, для Windows и macOS). В конце концов, мне нужно будет делать всё в рамках CI, так что решил сразу проверить, как всё будет происходить в реальности. Пайплайн простой, состоит из 3 шагов:1. сгенерировать файл с кодом на C (задача PowerShell):
New-Item "test.c" -ItemType File -Value "int Foo() { return 123; }"
(return 456; для macOS);2. собрать библиотеку (задача Command Line):
gcc -v -c test.c
gcc -v -shared -o test.dll test.o
(test.dylib для macOS);3. опубликовать артефакт с библиотекой (задача Publish Pipeline Artifacts).Итак, имеем файлы test.dll и test.dylib, предоставляющие одну и ту же функцию Foo, которая для Windows возвращает 123, а для macOS – 456, так что мы всегда сможем проверить корректность вызова и результата. Файлы положим рядом с DualLibClassLibrary.csproj.Теперь нужно понять, как добавить их в NuGet пакет так, чтобы после установки пакета они копировались в выходную директорию при сборке приложения, обеспечивая таким образом работу установленной библиотеки. Так как библиотека у нас кроссплатформенная и использует новый формат файла .csproj (SDK style), очень хочется там и объявить инструкции для упаковки файлов. Изучив немного вопрос, пришёл к такому содержимому .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45</TargetFrameworks>
<LangVersion>6</LangVersion>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup>
<PackageId>DualLibClassLibrary</PackageId>
<Version>1.0.0</Version>
<Authors>melanchall</Authors>
<Owners>melanchall</Owners>
<Description>Dual-lib class library</Description>
<Copyright>Copyright Melanchall 2021</Copyright>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Content Include="test.dll">
<Pack>true</Pack>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="test.dylib">
<Pack>true</Pack>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
Собираем пакет:dotnet pack .\DualLibClassLibrary.sln -c ReleaseДабы проверить установку пакета, создадим папку (я назвал её TestFeed) где-нибудь и укажем её в качестве источника пакетов в Visual Studio. Внутрь положим полученный файл DualLibClassLibrary.1.0.0.nupkg. Установку пакета проверим в старом добром классическом .NET Framework на Windows. Создаём консольное приложение, устанавливаем нашу библиотеку. В проекте действительно появляются два файла:
Файлы test.dll и test.dylib добавились из пакетаВыглядит обнадёживающе, пишем в файле Program.cs простой код:
static void Main(string[] args)
{
var result = DualLibClassLibrary.Class.Bar();
Console.WriteLine($"Result = {result}. Press any key to exit...");
Console.ReadKey();
}
Запускаем и грустим:
Программа не нашла файл test.dllЧто ж, заглянем в папку bin/Debug:
Файлы test.dll и test.dylib отсутствуют в выходной директории приложенияИ правда нет файлов. Как же так, <CopyToOutputDirectory> мы им указали, в структуре проекта файлы видны. Проверив содержимое .csproj нашего приложения, всё становится понятно:
В csproj полный беспорядок с добавленными файламиВо-первых, элемент <CopyToOutputDirectory> отсутствует, а во-вторых, по неведомой причине test.dylib добавился как элемент <None>, а test.dll как элемент <Content>. Остаётся только посмотреть содержимое файла .nupkg. Воспользовавшись программой NuGet Package Explorer, видим следующий манифест:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>DualLibClassLibrary</id>
<version>1.0.0</version>
<authors>melanchall</authors>
<owners></owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Dual-lib class library</description>
<copyright>Copyright Melanchall 2021</copyright>
<dependencies>
<group targetFramework=".NETFramework4.5" />
<group targetFramework=".NETStandard2.0" />
</dependencies>
<contentFiles>
<files include="any/net45/test.dll" buildAction="Content" />
<files include="any/netstandard2.0/test.dll" buildAction="Content" />
<files include="any/net45/test.dylib" buildAction="Content" />
<files include="any/netstandard2.0/test.dylib" buildAction="Content" />
</contentFiles>
</metadata>
</package>
Как видим, файлы добавились без атрибута copyToOutput, что печально (про атрибут можно почитать в таблице тут: Using the contentFiles element for content files).Копирование файлов в выходную директорию при сборке приложенияПолистав некоторое время просторы интернета в виде issues на GitHub, ответов на StackOverflow и официальной документации Microsoft, видоизменил элементы включения файлов в .csproj библиотеки:
<Content Include="test.dll">
<Pack>true</Pack>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
<PackagePath>contentFiles;content</PackagePath>
</Content>
<Content Include="test.dylib">
<Pack>true</Pack>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
<PackagePath>contentFiles;content</PackagePath>
</Content>
Элемент <PackageCopyToOutput> как раз должен привнести атрибут copyToOutput в манифест пакета. Кроме того, явно указал папки, куда нужно положить файлы, дабы избежать директорий вроде any. Подробнее о том, как всё это работает, можно почитать тут: Including content in a package.Собираем снова наш пакет и проверяем манифест:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>DualLibClassLibrary</id>
<version>1.0.1</version>
<authors>melanchall</authors>
<owners></owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Dual-lib class library</description>
<copyright>Copyright Melanchall 2021</copyright>
<dependencies>
<group targetFramework=".NETFramework4.5" />
<group targetFramework=".NETStandard2.0" />
</dependencies>
<contentFiles>
<files include="test.dll" buildAction="Content" copyToOutput="true" />
<files include="test.dylib" buildAction="Content" copyToOutput="true" />
</contentFiles>
</metadata>
</package>
Теперь всё выглядит куда лучше, простая структура файлов и атрибут copyToOutput на месте. Устанавливаем библиотеку в наше консольное приложение и запускаем:
copyToOutput ситуацию не спасаетИ снова неудача. Проверим в аналогичном консольном приложении, но на .NET 5:
Всё так же файлов нет в выходной директории приложенияКроме слегка изменённого текста исключения разницы не видно. Отписался в issue по итогу, на что мне ответили:
Please see our docs on contentFiles. It supports adding different content depending on project's target framework and language, and therefore needs files in a specific structure which your package is not currently using.
Оказалось, что я проглядел документацию, и, действительно, если файлы добавлять не по пути contentFiles, а по, например, contentFiles/any/netstandard2.0, то-таки да, автоматически создаётся .props файл, содержащий правильные элементы для файлов. Однако я свои исследования вёл до получения этого ответа, посему пошёл другим путём. И, как оказалось, верным, ибо подход с contentFiles исключает возможность использования пакета в .NET Framework приложениях, а я считаю, что этот сценарий обязан быть поддержан.Есть статья в документации Microsoft с подозрительно нужным заголовком: Creating native packages. Статья не сильно содержательная, однако кое-что полезное из неё можно почерпнуть. А именно, что можно сделать файл .targets, где мы и укажем <CopyToOutputDirectory> нашим файлам. Сам файл .targets мы включим в пакет вместе с нативными библиотеками. Сказано – сделано. Создаём файл DualLibClassLibrary.targets:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)test.dll">
<Link>test.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)test.dylib">
<Link>test.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
А в файле DualLibClassLibrary.csproj пропишем:
<ItemGroup>
<None Include="test.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackagePath>build\</PackagePath>
<Pack>true</Pack>
</None>
<None Include="test.dylib">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<PackagePath>build\</PackagePath>
<Pack>true</Pack>
</None>
<None Include="DualLibClassLibrary.targets">
<PackagePath>build\</PackagePath>
<Pack>true</Pack>
</None>
</ItemGroup>
Собираем версию 1.0.2, устанавливаем в наше консольное приложение .NET Framework и запускаем:
Ошибка уже другаяДанная ошибка может возникнуть из-за несоответствующей разрядности приложения и нативных сборок. Я собирал их на 64-битных системах, приложение запускаю также в 64-битной ОС. Что ж, продолжаем наше путешествие.Поддержка 32- и 64-битных процессовЕсли зайти в свойства проекта приложения в Visual Studio на вкладку Build, обнаружим такую опцию:
Процесс будет 32-битнымОказывается, для проектов .NET Framework она включена по умолчанию, а процесс приложения будет 32-битным даже на 64-битной операционной системе. Забавно, что в .NET Core/.NET 5+ опция по умолчанию выключена:
А в .NET Core опция выключенаМожно, конечно, выключить эту опцию, и приложение наконец напечатает верный результат:Result = 123000. Press any key to exit...Но, разумеется, это не решение по следующим причинам:
- не будет возможности использовать библиотеку в 32-битных процессах;
- придётся требовать от пользователей лишних действий в виде отключения галки;
- классический дефолтный сценарий (создать новое приложение .NET Framework безо всяких дополнительных манипуляций) оказывается нерабочим.
Конечно же, так никуда не годится, и проблему нужно победить. На самом деле, вариант тут очевиден: сделать нативные сборки для каждой операционной системы в двух вариантах – 32- и 64-битном. То есть поставка пакета чуть распухнет, вместо 2 платформозависимых библиотек внутри будут 4. Я в этом ничего плохого не вижу, ибо файлы всё равно небольшие, а потому буду продолжать именно с этим подходом (тем более, что иного не придумал).Немного расскажу о том, как собирал 32-битные версии библиотек. Как я упоминал выше, я произвожу сборку в конвейерах Azure DevOps через gcc. У gcc есть флаг -m32, который, по идее, должен как раз собрать 32-битную библиотеку. На сборочных агентах с macOS всё здорово, а вот на Windows получил нелицеприятные логи:C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libuser32.a when searching for -luser32...C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmsvcrt.a when searching for -lmsvcrtC:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmsvcrtcollect2.exe: error: ld returned 1 exit statusЗадав вопрос и на StackOverflow, и в Microsoft Developer Community, выяснилось, что на агентах Microsoft не предустановлен 32-битный MinGW, что и приводит к падению. Попробовав множество вариантов, я остановился на проекте brechtsanders/winlibs_mingw, придя к простому PowerShell скрипту:
Write-Host "Downloading winlibs..."
Invoke-WebRequest -Uri "https://github.com/brechtsanders/winlibs_mingw/releases/download/11.1.0-12.0.0-9.0.0-r1/winlibs-i686-posix-dwarf-gcc-11.1.0-mingw-w64-9.0.0-r1.zip" -OutFile "winlibs.zip"
Write-Host "Downloaded."
Write-Host "Extracting winlibs..."
Expand-Archive -LiteralPath 'winlibs.zip' -DestinationPath "winlibs"
Write-Host "Extracted."
Write-Host "Building DLL..."
$gccPath = Get-ChildItem -Path "winlibs" -File -Filter "i686-w64-mingw32-gcc.exe" -Recurse
& $gccPath.FullName -c test.c -m32
& $gccPath.FullName -shared -o test.dll test.o -m32
Write-Host "Built."
Используя поставляемый в составе архива компилятор i686-w64-mingw32-gcc.exe, удалось наконец-таки собрать 32-битный файл test.dll. Ура!Теперь осталось придумать, как заставить нашу библиотеку вызывать API либо из 32- либо из 64-битной сборки. Я думаю, варианты тут есть разные, я остановился на таком:
- собираем нативные библиотеки test32.dll, test64.dll, test32.dylib и test64.dylib;
- делаем абстрактный класс Api с абстрактными методами, соответствующими нашему managed API для внутреннего использования;
- делаем два наследника Api32 и Api64, в которых реализуем абстрактный API из родительского класса, вызывая unmanaged API из test32 и test64 соответственно;
- делаем класс ApiProvider, чьё свойство Api будет отдавать нам реализацию, соответствующую разрядности текущего процесса.
Приведу код файлов:Api.cs
namespace DualLibClassLibrary
{
internal abstract class Api
{
public abstract int Method();
}
}
Api32.cs
using System.Runtime.InteropServices;
namespace DualLibClassLibrary
{
internal sealed class Api32 : Api
{
[DllImport("test32")]
public static extern int Foo();
public override int Method()
{
return Foo();
}
}
}
Api64.cs
using System.Runtime.InteropServices;
namespace DualLibClassLibrary
{
internal sealed class Api64 : Api
{
[DllImport("test64")]
public static extern int Foo();
public override int Method()
{
return Foo();
}
}
}
ApiProvider.cs
using System;
namespace DualLibClassLibrary
{
internal static class ApiProvider
{
private static readonly bool Is64Bit = IntPtr.Size == 8;
private static Api _api;
public static Api Api
{
get
{
if (_api == null)
_api = Is64Bit ? (Api)new Api64() : new Api32();
return _api;
}
}
}
}
И тогда код нашего класса Class будет таким:
namespace DualLibClassLibrary
{
public static class Class
{
public static int Bar()
{
return ApiProvider.Api.Method() * 1000;
}
}
}
Собрав пакет (разумеется, обновив предварительно содержимое файлов DualLibClassLibrary.targets и DualLibClassLibrary.csproj, добавив новые файлы), убедимся, что метод нашей библиотеки работает корректно при любой разрядности процесса приложения.ЗаключениеЯ привёл полную хронологию моих мытарств касаемо создания NuGet пакета с платформозависимым API, но будет полезно кратко перечислить основные моменты (я же обещал инструкцию):
- создать нативные сборки, причём в двух вариантах: 32- и 64-битном;
- положить их рядом с проектом библиотеки (можно и в папку какую-то, главное путь указать к ним потом верный);
- добавить файл .targets, в котором для всех нативных сборок добавить элемент <CopyToOutputDirectory> с желаемым значением;
- в файле .csproj библиотеки прописать упаковку как нативных сборок, так и файла .targets (должен пойти в папку build пакета);
- реализовать механизм выбора нужной версии нативной сборки в зависимости от разрядности процесса.
Это всё. Солюшн нашей тестовой библиотеки можно взять отсюда: DualLibClassLibrary.zip. Решение было проверено в следующих сценариях на Windows и macOS:
- .NET Framework приложение;
- .NET Core / .NET 5 приложение;
- Self-contained приложение.
Касаемо проверки в 32- и 64-битном процессах – проверял только на Windows, не уверен, как проверить это на macOS.Стоит заметить, что .NET на данный момент поддерживает только десктопные операционные системы. Однако в .NET 6 заявляется поддержка также и мобильных платформ. Если честно, не уверен, сработает ли описанный в статье подход там. Думаю, что для iOS файл dylib спокойно подойдёт (или нет?), а касаемо Android нужно думать отдельно. Может, кто-то уже сталкивался и подскажет в комментариях?Спасибо за прочтение!
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Машинное обучение, Учебный процесс в IT, Карьера в IT-индустрии] И в науку, и в индустрию. Магистерские программы при поддержке JetBrains
- [Информационная безопасность, Программирование] Что под капотом у R-Vision Threat Intelligence Platform?
- [Системное администрирование, Программирование, IT-инфраструктура, Apache] Apache Pulsar как основа для системы очередей
- [Open source, JavaScript, Программирование, Серверное администрирование] zx – bash скрипты на javascript
- [.NET, API, Google API, C#, DIY или Сделай сам] How to be good in hackathons as a developer? Practice creating simple pet projects
- [Python, Программирование, Гаджеты, История IT] Декодирование сигнала с видеофона 1988 года выпуска (перевод)
- [Python, Программирование, Математика] Наглядно о том, как работает NumPy (перевод)
- [Open source, Python, Программирование, Учебный процесс в IT, Криптовалюты] Андрей Карпати: Bitcoin на Python (часть 1) (перевод)
- [Программирование, C++, Разработка под Linux] Интеграция пресетов CMake в Visual Studio и Visual Studio Code (перевод)
- [Python, Программирование, Алгоритмы] Новый выпуск «Скринкастов» вместе с MADE: много Python'а
Теги для поиска: #_programmirovanie (Программирование), #_.net, #_nuget, #_.net, #_krossplatformennost (кроссплатформенность), #_programmirovanie (
Программирование
), #_.net
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:28
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Я разрабатываю .NET библиотеку для работы с MIDI файлами и MIDI устройствами – DryWetMIDI. Большинство API библиотеки кроссплатформенное (в рамках поддерживаемых .NET систем, конечно же), однако работа с MIDI устройствами различна на разных операционных системах. На данный момент соответствующий API библиотеки работает только на Windows, однако есть большое желание обеспечить его работу и на других системах. Не буду бросаться в поддержку всего и вся, посему сначала собираюсь поддержать macOS, тем более что данная операционная система не менее популярна для работы с музыкой, а может даже и самая популярная в профессиональных кругах.Разумеется, нет смысла заниматься сразу реализацией реального API, проще проверить всё на маленьком примере. Именно так я и поступил, и хочу пройти путь до работающего решения ещё раз вместе с вами. Краткий список шагов будет приведён в конце статьи.Первые попыткиМоя библиотека, как и положено оной в мире .NET, поставляется в виде NuGet пакета. Поэтому я сразу понял, что в нём нужно будет также поставлять нативные библиотеки, предоставляющие API для конкретной операционной системы.В C# мы можем написать такое определение внешней функции: [DllImport("test")]
public static extern int Foo(); using System.Runtime.InteropServices;
namespace DualLibClassLibrary { public static class Class { [DllImport("test")] public static extern int Foo(); public static int Bar() { return Foo() * 1000; } } } int Foo() { return 123; }
int Foo() { return 456; }
New-Item "test.c" -ItemType File -Value "int Foo() { return 123; }"
gcc -v -c test.c
gcc -v -shared -o test.dll test.o <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <TargetFrameworks>netstandard2.0;net45</TargetFrameworks> <LangVersion>6</LangVersion> <Configurations>Debug;Release</Configurations> </PropertyGroup> <PropertyGroup> <PackageId>DualLibClassLibrary</PackageId> <Version>1.0.0</Version> <Authors>melanchall</Authors> <Owners>melanchall</Owners> <Description>Dual-lib class library</Description> <Copyright>Copyright Melanchall 2021</Copyright> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> </PropertyGroup> <ItemGroup> <Content Include="test.dll"> <Pack>true</Pack> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> <Content Include="test.dylib"> <Pack>true</Pack> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> </Project> Файлы test.dll и test.dylib добавились из пакетаВыглядит обнадёживающе, пишем в файле Program.cs простой код: static void Main(string[] args)
{ var result = DualLibClassLibrary.Class.Bar(); Console.WriteLine($"Result = {result}. Press any key to exit..."); Console.ReadKey(); } Программа не нашла файл test.dllЧто ж, заглянем в папку bin/Debug: Файлы test.dll и test.dylib отсутствуют в выходной директории приложенияИ правда нет файлов. Как же так, <CopyToOutputDirectory> мы им указали, в структуре проекта файлы видны. Проверив содержимое .csproj нашего приложения, всё становится понятно: В csproj полный беспорядок с добавленными файламиВо-первых, элемент <CopyToOutputDirectory> отсутствует, а во-вторых, по неведомой причине test.dylib добавился как элемент <None>, а test.dll как элемент <Content>. Остаётся только посмотреть содержимое файла .nupkg. Воспользовавшись программой NuGet Package Explorer, видим следующий манифест: <?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> <metadata> <id>DualLibClassLibrary</id> <version>1.0.0</version> <authors>melanchall</authors> <owners></owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>Dual-lib class library</description> <copyright>Copyright Melanchall 2021</copyright> <dependencies> <group targetFramework=".NETFramework4.5" /> <group targetFramework=".NETStandard2.0" /> </dependencies> <contentFiles> <files include="any/net45/test.dll" buildAction="Content" /> <files include="any/netstandard2.0/test.dll" buildAction="Content" /> <files include="any/net45/test.dylib" buildAction="Content" /> <files include="any/netstandard2.0/test.dylib" buildAction="Content" /> </contentFiles> </metadata> </package> <Content Include="test.dll">
<Pack>true</Pack> <CopyToOutputDirectory>Always</CopyToOutputDirectory> <PackageCopyToOutput>true</PackageCopyToOutput> <PackagePath>contentFiles;content</PackagePath> </Content> <Content Include="test.dylib"> <Pack>true</Pack> <CopyToOutputDirectory>Always</CopyToOutputDirectory> <PackageCopyToOutput>true</PackageCopyToOutput> <PackagePath>contentFiles;content</PackagePath> </Content> <?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> <metadata> <id>DualLibClassLibrary</id> <version>1.0.1</version> <authors>melanchall</authors> <owners></owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>Dual-lib class library</description> <copyright>Copyright Melanchall 2021</copyright> <dependencies> <group targetFramework=".NETFramework4.5" /> <group targetFramework=".NETStandard2.0" /> </dependencies> <contentFiles> <files include="test.dll" buildAction="Content" copyToOutput="true" /> <files include="test.dylib" buildAction="Content" copyToOutput="true" /> </contentFiles> </metadata> </package> copyToOutput ситуацию не спасаетИ снова неудача. Проверим в аналогичном консольном приложении, но на .NET 5: Всё так же файлов нет в выходной директории приложенияКроме слегка изменённого текста исключения разницы не видно. Отписался в issue по итогу, на что мне ответили: Please see our docs on contentFiles. It supports adding different content depending on project's target framework and language, and therefore needs files in a specific structure which your package is not currently using.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <None Include="$(MSBuildThisFileDirectory)test.dll"> <Link>test.dll</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="$(MSBuildThisFileDirectory)test.dylib"> <Link>test.dylib</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup> </Project> <ItemGroup>
<None Include="test.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <PackagePath>build\</PackagePath> <Pack>true</Pack> </None> <None Include="test.dylib"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <PackagePath>build\</PackagePath> <Pack>true</Pack> </None> <None Include="DualLibClassLibrary.targets"> <PackagePath>build\</PackagePath> <Pack>true</Pack> </None> </ItemGroup> Ошибка уже другаяДанная ошибка может возникнуть из-за несоответствующей разрядности приложения и нативных сборок. Я собирал их на 64-битных системах, приложение запускаю также в 64-битной ОС. Что ж, продолжаем наше путешествие.Поддержка 32- и 64-битных процессовЕсли зайти в свойства проекта приложения в Visual Studio на вкладку Build, обнаружим такую опцию: Процесс будет 32-битнымОказывается, для проектов .NET Framework она включена по умолчанию, а процесс приложения будет 32-битным даже на 64-битной операционной системе. Забавно, что в .NET Core/.NET 5+ опция по умолчанию выключена: А в .NET Core опция выключенаМожно, конечно, выключить эту опцию, и приложение наконец напечатает верный результат:Result = 123000. Press any key to exit...Но, разумеется, это не решение по следующим причинам:
Write-Host "Downloading winlibs..."
Invoke-WebRequest -Uri "https://github.com/brechtsanders/winlibs_mingw/releases/download/11.1.0-12.0.0-9.0.0-r1/winlibs-i686-posix-dwarf-gcc-11.1.0-mingw-w64-9.0.0-r1.zip" -OutFile "winlibs.zip" Write-Host "Downloaded." Write-Host "Extracting winlibs..." Expand-Archive -LiteralPath 'winlibs.zip' -DestinationPath "winlibs" Write-Host "Extracted." Write-Host "Building DLL..." $gccPath = Get-ChildItem -Path "winlibs" -File -Filter "i686-w64-mingw32-gcc.exe" -Recurse & $gccPath.FullName -c test.c -m32 & $gccPath.FullName -shared -o test.dll test.o -m32 Write-Host "Built."
namespace DualLibClassLibrary
{ internal abstract class Api { public abstract int Method(); } } using System.Runtime.InteropServices;
namespace DualLibClassLibrary { internal sealed class Api32 : Api { [DllImport("test32")] public static extern int Foo(); public override int Method() { return Foo(); } } } using System.Runtime.InteropServices;
namespace DualLibClassLibrary { internal sealed class Api64 : Api { [DllImport("test64")] public static extern int Foo(); public override int Method() { return Foo(); } } } using System;
namespace DualLibClassLibrary { internal static class ApiProvider { private static readonly bool Is64Bit = IntPtr.Size == 8; private static Api _api; public static Api Api { get { if (_api == null) _api = Is64Bit ? (Api)new Api64() : new Api32(); return _api; } } } } namespace DualLibClassLibrary
{ public static class Class { public static int Bar() { return ApiProvider.Api.Method() * 1000; } } }
=========== Источник: habr.com =========== Похожие новости:
Программирование ), #_.net |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:28
Часовой пояс: UTC + 5