Load specific native libraries on desktop/netfx

This fixes 2 issues:
- loading multiple versions of libSkiaSharp (#1252)
- resolving the issue with 32/64 bit dll (#713)
This commit is contained in:
Matthew Leibowitz 2020-06-19 01:49:29 +02:00
Родитель c23eab0bf9
Коммит 7cda786b8a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: ECDB25CC0E22FC46
11 изменённых файлов: 8998 добавлений и 106 удалений

Просмотреть файл

@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
#if HARFBUZZ
namespace HarfBuzzSharp
#else
namespace SkiaSharp
#endif
{
#if USE_DELEGATES || USE_LIBRARY_LOADER
internal static class LibraryLoader
{
static LibraryLoader ()
{
if (PlatformConfiguration.IsWindows)
Extension = ".dll";
else if (PlatformConfiguration.IsMac)
Extension = ".dylib";
else
Extension = ".so";
}
public static string Extension { get; }
public static IntPtr LoadLocalLibrary<T> (string libraryName)
{
var libraryPath = GetLibraryPath (libraryName);
var handle = LoadLibrary (libraryPath);
if (handle == IntPtr.Zero)
throw new DllNotFoundException ($"Unable to load library '{libraryName}'.");
return handle;
static string GetLibraryPath (string libraryName)
{
var arch = IntPtr.Size == 8 ? "x64" : "x86";
var libWithExt = libraryName;
if (!libraryName.EndsWith (Extension, StringComparison.OrdinalIgnoreCase))
libWithExt += Extension;
// 1. try alongside managed assembly
var path = typeof (T).Assembly.Location;
if (!string.IsNullOrEmpty (path)) {
path = Path.GetDirectoryName (path);
// 1.1 in platform sub dir
path = Path.Combine (path, arch, libWithExt);
if (File.Exists (path))
return path;
// 1.2 in root
path = Path.Combine (path, libWithExt);
if (File.Exists (path))
return path;
}
// 2. try current directory
path = Directory.GetCurrentDirectory ();
if (!string.IsNullOrEmpty (path)) {
// 2.1 in platform sub dir
path = Path.Combine (path, arch, libWithExt);
if (File.Exists (path))
return path;
// 2.2 in root
path = Path.Combine (path, libWithExt);
if (File.Exists (path))
return path;
}
// 3. use PATH or default loading mechanism
return libraryName;
}
}
public static T GetSymbolDelegate<T> (IntPtr library, string name)
where T : Delegate
{
var symbol = GetSymbol (library, name);
if (symbol == IntPtr.Zero)
throw new EntryPointNotFoundException ($"Unable to load symbol '{name}'.");
#if __NET_45__
return (T)Marshal.GetDelegateForFunctionPointer (symbol, typeof (T));
#else
return Marshal.GetDelegateForFunctionPointer<T> (symbol);
#endif
}
public static IntPtr LoadLibrary (string libraryName)
{
if (string.IsNullOrEmpty (libraryName))
throw new ArgumentNullException (nameof (libraryName));
IntPtr handle;
if (PlatformConfiguration.IsWindows)
handle = Win32.LoadLibrary (libraryName);
else if (PlatformConfiguration.IsLinux)
handle = Linux.dlopen (libraryName);
else if (PlatformConfiguration.IsMac)
handle = Mac.dlopen (libraryName);
else
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to load library '{libraryName}'.");
return handle;
}
public static IntPtr GetSymbol (IntPtr library, string symbolName)
{
if (string.IsNullOrEmpty (symbolName))
throw new ArgumentNullException (nameof (symbolName));
IntPtr handle;
if (PlatformConfiguration.IsWindows)
handle = Win32.GetProcAddress (library, symbolName);
else if (PlatformConfiguration.IsLinux)
handle = Linux.dlsym (library, symbolName);
else if (PlatformConfiguration.IsMac)
handle = Mac.dlsym (library, symbolName);
else
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to load symbol '{symbolName}' from library {library}.");
return handle;
}
public static void FreeLibrary (IntPtr library)
{
if (library == IntPtr.Zero)
return;
if (PlatformConfiguration.IsWindows)
Win32.FreeLibrary (library);
else if (PlatformConfiguration.IsLinux)
Linux.dlclose (library);
else if (PlatformConfiguration.IsMac)
Mac.dlclose (library);
else
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to close library '{library}'.");
}
#pragma warning disable IDE1006 // Naming Styles
private static class Mac
{
private const string SystemLibrary = "/usr/lib/libSystem.dylib";
private const int RTLD_LAZY = 1;
private const int RTLD_NOW = 2;
public static IntPtr dlopen (string path, bool lazy = true) =>
dlopen (path, lazy ? RTLD_LAZY : RTLD_NOW);
[DllImport (SystemLibrary)]
public static extern IntPtr dlopen (string path, int mode);
[DllImport (SystemLibrary)]
public static extern IntPtr dlsym (IntPtr handle, string symbol);
[DllImport (SystemLibrary)]
public static extern void dlclose (IntPtr handle);
}
private static class Linux
{
private const string SystemLibrary = "libdl.so";
private const int RTLD_LAZY = 1;
private const int RTLD_NOW = 2;
public static IntPtr dlopen (string path, bool lazy = true) =>
dlopen (path, lazy ? RTLD_LAZY : RTLD_NOW);
[DllImport (SystemLibrary)]
public static extern IntPtr dlopen (string path, int mode);
[DllImport (SystemLibrary)]
public static extern IntPtr dlsym (IntPtr handle, string symbol);
[DllImport (SystemLibrary)]
public static extern void dlclose (IntPtr handle);
}
private static class Win32
{
private const string SystemLibrary = "Kernel32.dll";
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr LoadLibrary (string lpFileName);
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr GetProcAddress (IntPtr hModule, string lpProcName);
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern void FreeLibrary (IntPtr hModule);
}
#pragma warning restore IDE1006 // Naming Styles
}
#endif
}

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
#if HARFBUZZ
@ -11,20 +10,62 @@ namespace SkiaSharp
internal static class PlatformConfiguration
{
public static bool IsUnix { get; }
public static bool IsWindows { get; }
public static bool IsMac { get; }
public static bool IsLinux { get; }
static PlatformConfiguration ()
{
#if WINDOWS_UWP
IsMac = false;
IsLinux = false;
IsUnix = false;
IsWindows = true;
#elif NET_STANDARD
IsUnix = RuntimeInformation.IsOSPlatform (OSPlatform.OSX) || RuntimeInformation.IsOSPlatform (OSPlatform.Linux);
#elif NET_STANDARD || __NET_46__
IsMac = RuntimeInformation.IsOSPlatform (OSPlatform.OSX);
IsLinux = RuntimeInformation.IsOSPlatform (OSPlatform.Linux);
IsUnix = IsMac || IsLinux;
IsWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows);
#else
IsUnix = Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix;
IsWindows = !IsUnix;
IsMac = IsUnix && MacPlatformDetector.IsMac.Value;
IsLinux = IsUnix && !IsMac;
#endif
}
#if !NET_STANDARD && !__NET_46__
#pragma warning disable IDE1006 // Naming Styles
private static class MacPlatformDetector
{
internal static readonly Lazy<bool> IsMac = new Lazy<bool> (IsRunningOnMac);
[DllImport ("libc")]
static extern int uname (IntPtr buf);
static bool IsRunningOnMac ()
{
IntPtr buf = IntPtr.Zero;
try {
buf = Marshal.AllocHGlobal (8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname (buf) == 0) {
string os = Marshal.PtrToStringAnsi (buf);
if (os == "Darwin")
return true;
}
} catch {
} finally {
if (buf != IntPtr.Zero)
Marshal.FreeHGlobal (buf);
}
return false;
}
}
#pragma warning restore IDE1006 // Naming Styles
#endif
}
}

Просмотреть файл

@ -1,4 +1,6 @@
namespace SkiaSharp
using System;
namespace SkiaSharp
{
internal partial class SkiaApi
{
@ -21,5 +23,18 @@
#else
private const string SKIA = "libSkiaSharp";
#endif
#if USE_DELEGATES
private static IntPtr libSkiaSharpHandle;
private static T Get<T> (string name)
where T : Delegate
{
if (libSkiaSharpHandle == IntPtr.Zero)
libSkiaSharpHandle = LibraryLoader.LoadLocalLibrary<SkiaApi> (SKIA);
return LibraryLoader.GetSymbolDelegate<T> (libSkiaSharpHandle, name);
}
#endif
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -152,7 +152,7 @@ namespace SkiaSharp
if (data.Length == 0)
return string.Empty;
#if __DESKTOP__
#if __NET_45__
// TODO: improve this copy for old .NET 4.5
var array = data.ToArray ();
return encoding switch

Просмотреть файл

@ -8,8 +8,14 @@
<PropertyGroup Condition="$(TargetFramework.StartsWith('netstandard'))">
<DefineConstants>$(DefineConstants);NET_STANDARD</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
<DefineConstants>$(DefineConstants);USE_DELEGATES;__DESKTOP__</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('net45'))">
<DefineConstants>$(DefineConstants);__DESKTOP__</DefineConstants>
<DefineConstants>$(DefineConstants);__NET_45__</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('net46'))">
<DefineConstants>$(DefineConstants);__NET_46__</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="System.IO.UnmanagedMemoryStream" Version="4.3.0" />

Просмотреть файл

@ -100,10 +100,14 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\output\native\windows\$(Platform)\libSkiaSharp.dll" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\$(Platform)\libSkiaSharp.dll') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\$(Platform)\libSkiaSharp.pdb" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\$(Platform)\libSkiaSharp.pdb') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\x64\libSkiaSharp.dll" Link="x64\libSkiaSharp.dll" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\x64\libSkiaSharp.dll') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\x64\libSkiaSharp.pdb" Link="x64\libSkiaSharp.pdb" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\x64\libSkiaSharp.pdb') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\x86\libSkiaSharp.dll" Link="x86\libSkiaSharp.dll" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\x86\libSkiaSharp.dll') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\x86\libSkiaSharp.pdb" Link="x86\libSkiaSharp.pdb" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\x64\libSkiaSharp.pdb') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\$(Platform)\libHarfBuzzSharp.dll" CopyToOutputDirectory="Always" Visible="false"
Condition=" Exists('..\..\output\native\windows\$(Platform)\libHarfBuzzSharp.dll') or '$(IsWindows)' == 'true' " />
<Content Include="..\..\output\native\windows\$(Platform)\libHarfBuzzSharp.pdb" CopyToOutputDirectory="Always" Visible="false"

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<DefineConstants>$(DefineConstants);NET_STANDARD</DefineConstants>
<DefineConstants>$(DefineConstants);NET_STANDARD;USE_LIBRARY_LOADER</DefineConstants>
<RootNamespace>SkiaSharp.Tests</RootNamespace>
<AssemblyName>SkiaSharp.Tests</AssemblyName>
<SkipGenerateAssemblyVersionInfo>true</SkipGenerateAssemblyVersionInfo>
@ -21,6 +21,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\Tests\**\*.cs" Link="%(RecursiveDir)%(FileName)%(Extension)" />
<Compile Include="..\..\binding\Binding.Shared\LibraryLoader.cs" Link="PlatformUtils\LibraryLoader.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Content\**\*" Link="%(RecursiveDir)%(FileName)%(Extension)" CopyToOutputDirectory="Always" />

Просмотреть файл

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace SkiaSharp.Tests
{
@ -11,10 +10,10 @@ namespace SkiaSharp.Tests
protected const string GpuCategory = "GPU";
protected const string MatchCharacterCategory = "MatchCharacter";
protected static bool IsLinux;
protected static bool IsMac;
protected static bool IsUnix;
protected static bool IsWindows;
protected static bool IsLinux = PlatformConfiguration.IsLinux;
protected static bool IsMac = PlatformConfiguration.IsMac;
protected static bool IsUnix = PlatformConfiguration.IsUnix;
protected static bool IsWindows = PlatformConfiguration.IsWindows;
protected static bool IsRuntimeMono;
@ -54,19 +53,6 @@ namespace SkiaSharp.Tests
}
#endif
// set the OS fields
#if NET_STANDARD
IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
IsUnix = IsLinux || IsMac;
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#else
IsMac = MacPlatformDetector.IsMac.Value;
IsUnix = Environment.OSVersion.Platform == PlatformID.Unix || IsMac;
IsLinux = IsUnix && !IsMac;
IsWindows = !IsUnix;
#endif
IsRuntimeMono = Type.GetType("Mono.Runtime") != null;
// set the test fields
@ -82,71 +68,5 @@ namespace SkiaSharp.Tests
GC.Collect();
GC.WaitForPendingFinalizers();
}
private static class MacPlatformDetector
{
internal static readonly Lazy<bool> IsMac = new Lazy<bool>(IsRunningOnMac);
[DllImport("libc")]
static extern int uname(IntPtr buf);
static bool IsRunningOnMac()
{
IntPtr buf = IntPtr.Zero;
try
{
buf = Marshal.AllocHGlobal(8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname(buf) == 0)
{
string os = Marshal.PtrToStringAnsi(buf);
if (os == "Darwin")
return true;
}
}
catch
{
}
finally
{
if (buf != IntPtr.Zero)
Marshal.FreeHGlobal(buf);
}
return false;
}
}
public static class MacDynamicLibraries
{
private const string SystemLibrary = "/usr/lib/libSystem.dylib";
[DllImport(SystemLibrary)]
public static extern IntPtr dlopen(string path, int mode);
[DllImport(SystemLibrary)]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport(SystemLibrary)]
public static extern void dlclose(IntPtr handle);
}
public static class LinuxDynamicLibraries
{
private const string SystemLibrary = "libdl.so";
[DllImport(SystemLibrary)]
public static extern IntPtr dlopen(string path, int mode);
[DllImport(SystemLibrary)]
public static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport(SystemLibrary)]
public static extern void dlclose(IntPtr handle);
}
public static class WindowsDynamicLibraries
{
private const string SystemLibrary = "Kernel32.dll";
[DllImport(SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport(SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
[DllImport(SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
public static extern void FreeLibrary(IntPtr hModule);
}
}
}

Просмотреть файл

@ -37,21 +37,21 @@ namespace SkiaSharp.Tests
ctx.MakeCurrent();
if (IsMac) {
var lib = MacDynamicLibraries.dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", 1);
var lib = LibraryLoader.LoadLibrary("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib");
var glInterface = GRGlInterface.Create(name => {
return MacDynamicLibraries.dlsym(lib, name);
return LibraryLoader.GetSymbol(lib, name);
});
Assert.NotNull(glInterface);
Assert.True(glInterface.Validate());
MacDynamicLibraries.dlclose(lib);
LibraryLoader.FreeLibrary(lib);
} else if (IsWindows) {
var lib = WindowsDynamicLibraries.LoadLibrary("opengl32.dll");
var lib = LibraryLoader.LoadLibrary("opengl32.dll");
var glInterface = GRGlInterface.Create(name => {
var ptr = WindowsDynamicLibraries.GetProcAddress(lib, name);
var ptr = LibraryLoader.GetSymbol(lib, name);
if (ptr == IntPtr.Zero) {
ptr = wglGetProcAddress(name);
}
@ -61,7 +61,7 @@ namespace SkiaSharp.Tests
Assert.NotNull(glInterface);
Assert.True(glInterface.Validate());
WindowsDynamicLibraries.FreeLibrary(lib);
LibraryLoader.FreeLibrary(lib);
} else if (IsLinux) {
var glInterface = GRGlInterface.Create(name => {
return glXGetProcAddress(name);

Просмотреть файл

@ -333,14 +333,11 @@ namespace SkiaSharpGenerator
{
Log?.LogVerbose($" {function.Name}");
writer.WriteLine();
writer.WriteLine($"\t\t// {function}");
writer.WriteLine($"\t\t[DllImport ({config.DllName}, CallingConvention = CallingConvention.Cdecl)]");
var name = function.Name;
functionMappings.TryGetValue(name, out var funcMap);
var paramsList = new List<string>();
var paramNamesList = new List<string>();
for (var i = 0; i < function.Parameters.Count; i++)
{
var p = function.Parameters[i];
@ -352,9 +349,11 @@ namespace SkiaSharpGenerator
if (funcMap != null && funcMap.Parameters.TryGetValue(i.ToString(), out var newT))
t = newT;
paramsList.Add($"{t} {n}");
paramNamesList.Add(n);
}
var returnType = GetType(function.ReturnType);
var retAttr = "";
if (funcMap != null && funcMap.Parameters.TryGetValue("-1", out var newR))
{
returnType = newR;
@ -362,9 +361,27 @@ namespace SkiaSharpGenerator
else if (GetCppType(function.ReturnType) == "bool")
{
returnType = "bool";
writer.WriteLine($"\t\t[return: MarshalAs (UnmanagedType.I1)]");
retAttr = $"[return: MarshalAs (UnmanagedType.I1)]";
}
writer.WriteLine();
writer.WriteLine($"\t\t// {function}");
writer.WriteLine($"\t\t#if !USE_DELEGATES");
writer.WriteLine($"\t\t[DllImport ({config.DllName}, CallingConvention = CallingConvention.Cdecl)]");
if (!string.IsNullOrEmpty(retAttr))
writer.WriteLine($"\t\t{retAttr}");
writer.WriteLine($"\t\tinternal static extern {returnType} {name} ({string.Join(", ", paramsList)});");
writer.WriteLine($"\t\t#else");
writer.WriteLine($"\t\tprivate partial class Delegates {{");
writer.WriteLine($"\t\t\t[UnmanagedFunctionPointer(CallingConvention.Cdecl)]");
if (!string.IsNullOrEmpty(retAttr))
writer.WriteLine($"\t\t\t{retAttr}");
writer.WriteLine($"\t\t\tinternal delegate {returnType} {name} ({string.Join(", ", paramsList)});");
writer.WriteLine($"\t\t}}");
writer.WriteLine($"\t\tprivate static Delegates.{name} {name}_delegate;");
writer.WriteLine($"\t\tinternal static {returnType} {name} ({string.Join(", ", paramsList)}) =>");
writer.WriteLine($"\t\t\t({name}_delegate ??= Get<Delegates.{name}>(\"{name}\")).Invoke({string.Join(", ", paramNamesList)});");
writer.WriteLine($"\t\t#endif");
}
writer.WriteLine();
writer.WriteLine($"\t\t#endregion");