SkiaSharp
SkiaSharp copied to clipboard
[BUG] VS build error when referencing SkiaSharp from an SDK style csproj targetting .NET framework 4.8
Description
Visual Studio build error when referencing SkiaSharp from an SDK style csproj targetting .NET framework 4.8
Code
My .csproj file is as follows.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.3" />
</ItemGroup>
</Project>
I then try and compile it using VS 2022 and receive a build error.
Expected Behavior
The project builds with no errors.
Actual Behavior
I receive the following build error:
Error NETSDK1022 Duplicate 'Content' items were included. The .NET SDK includes 'Content' items from your project directory by default. You can either remove these items from your project file, or set the 'EnableDefaultContentItems' property to 'false' if you want to explicitly include them in your project file. For more information, see https://aka.ms/sdkimplicititems. The duplicate items were: 'C:\Users\XXX\.nuget\packages\skiasharp.nativeassets.win32\2.88.3\buildTransitive\net462\..\..\runtimes\win-x86\native\libSkiaSharp.dll'; 'C:\Users\XXX\.nuget\packages\skiasharp.nativeassets.win32\2.88.3\buildTransitive\net462\..\..\runtimes\win-x64\native\libSkiaSharp.dll'; 'C:\Users\XXX\.nuget\packages\skiasharp.nativeassets.win32\2.88.3\buildTransitive\net462\..\..\runtimes\win-arm64\native\libSkiaSharp.dll' ConsoleApp3 C:\Program Files\dotnet\sdk\7.0.203\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.DefaultItems.Shared.targets
Basic Information
- Version with issue: 2.88.3
- Last known good version: N/A
- IDE: Visual Studio 2022
- Platform Target Frameworks:
- Windows Classic: Windows Enterprise 10
Other information
If I target net7.0 the project build ok. Also using a traditional non-SDK style project targeting .NET framework 4.8 this also compiles.
Hi, @heathdavies-eaton , Did you solve this issue in the end?
Hello @ArlenLi , no I didn't find a solution. In the end I just had to use a non-SDK style project.
@ArlenLi @heathdavies-eaton,
we are suffering the same problem, maybe a workaround would be excluding the macOS Nuget package native assets, because net48 is not platform independent. A valid temporary fix would be removing SkiaSharp.NativeAssets.macOS as transitive package.
But I'm not an expert for nuget packages:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.6" ExcludeAssets="buildTransitive" />
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.6" />
</ItemGroup>
</Project>
Maybe the correct bug fix would be removing SkiaSharp.NativeAssets.macOS nuget package dependency for net462 in SkiaSharp.nuspec:
https://github.com/mono/SkiaSharp/blob/e2c5c86249621857107c779af0f79b4d06613766/nuget/SkiaSharp.nuspec#L33C1-L33C1
Same issue. I had to solve it as follows in my csproj:
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.8" GeneratePathProperty="true" EcludeAssets="buildTransitive" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" GeneratePathProperty="true" ExcludeAssets="all" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.8" GeneratePathProperty="true" ExcludeAssets="all"/>
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.8" GeneratePathProperty="true" ExcludeAssets="all" />
</ItemGroup>
<!-- hack for SkiaSharp, so the native dlls go in the right place -->
<Import Project="$(PkgSkiaSharp_NativeAssets_Linux)\build\net462\SkiaSharp.NativeAssets.Linux.targets" Condition="'$(TargetFramework)' != ''" />
<Import Project="$(PkgSkiaSharp_NativeAssets_macOS)\build\net462\SkiaSharp.NativeAssets.macOS.targets" Condition="'$(TargetFramework)' != ''" />
<Import Project="$(PkgSkiaSharp_NativeAssets_Win32)\build\net462\SkiaSharp.NativeAssets.Win32.targets" Condition="'$(TargetFramework)' != ''" />
Then, in my code I had to tell .NET where to load the native dlls as follows:
First I made a class that set's up a DllImportResolver when needed:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace SolidCP.Providers.OS
{
public class SkiaSharp
{
public bool IsLinuxMusl
{
get
{
if (!OSInfo.IsLinux) return false;
return OS.Shell.Default.Exec("ldd /bin/ls").OutputAndError().Result.Contains("musl");
}
}
static readonly SkiaSharp Current = new SkiaSharp();
static Dictionary<string, IntPtr> loadedNativeDlls = new Dictionary<string, IntPtr>();
public IntPtr SkiaDllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName.Contains("SkiaSharp"))
{
lock (this)
{
IntPtr dll;
if (loadedNativeDlls.TryGetValue(libraryName, out dll)) return dll;
var runtimeInformation = typeof(RuntimeInformation);
var runtimeIdentifier = (string?)runtimeInformation.GetProperty("RuntimeIdentifier")?.GetValue(null);
if (runtimeIdentifier == "linux-x64" && IsLinuxMusl) runtimeIdentifier = "linux-musl-x64";
runtimeIdentifier = runtimeIdentifier.Replace("linux-", "");
var currentDllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
string libraryFileName = libraryName;
if (!libraryFileName.EndsWith(".so")) libraryFileName += ".so";
if (!libraryFileName.StartsWith("lib")) libraryFileName = "lib" + libraryFileName;
var nativeDllPath = Path.Combine(currentDllPath, runtimeIdentifier, libraryFileName);
if (File.Exists(nativeDllPath))
{
// call NativeLibrary.Load via reflection, becuase it's not available in NET Standard
var nativeLibrary = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices");
var load = nativeLibrary.GetMethod("Load", new Type[] { typeof(string), typeof(Assembly), typeof(DllImportSearchPath?) });
dll = (IntPtr)load?.Invoke(null, new object[] { nativeDllPath, assembly, searchPath });
loadedNativeDlls.Add(libraryName, dll);
Console.WriteLine($"Loaded native library: {nativeDllPath}");
return dll;
}
}
}
// Otherwise, fallback to default import resolver.
return IntPtr.Zero;
}
static bool nativeSkiaDllLoaded = false;
public static void LoadNativeDlls()
{
if (nativeSkiaDllLoaded) return;
nativeSkiaDllLoaded = true;
if (OSInfo.IsLinux)
{
// call NativeLibrary.SetDllImportResolver via reflection, becuase it's not available in NET Standard
var nativeLibrary = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices");
var dllImportResolver = Type.GetType("System.Runtime.InteropServices.DllImportResolver, System.Runtime.InteropServices");
Assembly skiaSharp = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "SkiaSharp");
if (skiaSharp == null)
{
skiaSharp = Assembly.Load("SkiaSharp");
}
var setDllImportResolver = nativeLibrary.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolver });
//var importResolverMethod = this.GetType().GetMethod(nameof(SkiaDllImportResolver));
var skiaDllImportResolver = Delegate.CreateDelegate(dllImportResolver, Current, nameof(SkiaDllImportResolver));
setDllImportResolver?.Invoke(null, new object[] { skiaSharp, skiaDllImportResolver });
Console.WriteLine("Added SkiaSharp DllImportResolver");
}
}
}
}
Then, always before you run SkiaSharp in your code call SkiaSharp.LoadNativeDlls()
In the above code, OS.Shell.Default.Exec("ldd /bin/ls").OutputAndError().Result.Contains("musl");,
this is a call to one of my library methods, what it does it calls a new process "ldd" with arguments "/bin/ls" and checks if the output contains "musl". Thats how you check if you're running on a Linux with musl c library, in which case you need to load the linux-musl-x64 .so SkiaSharp native library. You have to replace that line in my code with a proper call to Process.Start and then examine it's output. Also the call to OSInfo.IsLinux you have to replace with RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux);
Probably when you do a dotnet publish you don't need all this, but my project runs on both .NET FX and .NET Core and my project cannot use `dotnet publish´