Open heathdavies-eaton opened 1 year ago
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:
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´
I am getting the same build error. My .NET Framework 4.8 web project referenced another local project, which referenced a nuget package, which referenced another nuget package, which referenced SkiaSharp. I can probably reproduce the issue in a sample repo if it would help.
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.
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:
Basic Information
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.