Skip to content

Commit d15cb24

Browse files
authored
Load specific native libraries on desktop/netfx (#1342)
* Changed the library loading logic for .NET Framework / Mono * Using delegates with LoadLibrary/GetProcAddress or dlopen/dlsym * Add logic to search around to fine the native library * First next to SkiaSharp.dll, then current directory, then PATH * Check <dir>[/<arch>]/libSkiaSharp.<ext> * Fixes #713 * Added version APIs to the native library * Fixes #1252 * Added a SkiaSharpVersion helper type to support native library version checks * Can be used to check if the native library is compatible with the managed library * APIs can return milestone and iteration * Improves the build .targets files that copy libSkiaSharp
2 parents 58348e1 + 335d82b commit d15cb24

File tree

36 files changed

+9392
-372
lines changed

36 files changed

+9392
-372
lines changed

VERSIONS.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ SharpVk release 0.4.2
2424
OpenTK.GLControl reference 1.1.2349.61993
2525
Xamarin.Forms reference 4.4.0.991757
2626

27+
# native milestones
28+
# this is related to the API versions, not the library versions
29+
# - milestone: the skia milestone determined by Google/Chromium
30+
# - increment: the C API version increment caused by new APIs
31+
libSkiaSharp milestone 80
32+
libSkiaSharp increment 0
33+
2734
# native sonames
2835
libSkiaSharp soname 80.0.0
2936
HarfBuzz soname 0.20601.0
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
5+
#if HARFBUZZ
6+
namespace HarfBuzzSharp
7+
#else
8+
namespace SkiaSharp
9+
#endif
10+
{
11+
#if USE_DELEGATES || USE_LIBRARY_LOADER
12+
internal static class LibraryLoader
13+
{
14+
static LibraryLoader ()
15+
{
16+
if (PlatformConfiguration.IsWindows)
17+
Extension = ".dll";
18+
else if (PlatformConfiguration.IsMac)
19+
Extension = ".dylib";
20+
else
21+
Extension = ".so";
22+
}
23+
24+
public static string Extension { get; }
25+
26+
public static IntPtr LoadLocalLibrary<T> (string libraryName)
27+
{
28+
var libraryPath = GetLibraryPath (libraryName);
29+
30+
var handle = LoadLibrary (libraryPath);
31+
if (handle == IntPtr.Zero)
32+
throw new DllNotFoundException ($"Unable to load library '{libraryName}'.");
33+
34+
return handle;
35+
36+
static string GetLibraryPath (string libraryName)
37+
{
38+
var arch = IntPtr.Size == 8 ? "x64" : "x86";
39+
40+
var libWithExt = libraryName;
41+
if (!libraryName.EndsWith (Extension, StringComparison.OrdinalIgnoreCase))
42+
libWithExt += Extension;
43+
44+
// 1. try alongside managed assembly
45+
var path = typeof (T).Assembly.Location;
46+
if (!string.IsNullOrEmpty (path)) {
47+
path = Path.GetDirectoryName (path);
48+
// 1.1 in platform sub dir
49+
var lib = Path.Combine (path, arch, libWithExt);
50+
if (File.Exists (lib))
51+
return lib;
52+
// 1.2 in root
53+
lib = Path.Combine (path, libWithExt);
54+
if (File.Exists (lib))
55+
return lib;
56+
}
57+
58+
// 2. try current directory
59+
path = Directory.GetCurrentDirectory ();
60+
if (!string.IsNullOrEmpty (path)) {
61+
// 2.1 in platform sub dir
62+
var lib = Path.Combine (path, arch, libWithExt);
63+
if (File.Exists (lib))
64+
return lib;
65+
// 2.2 in root
66+
lib = Path.Combine (lib, libWithExt);
67+
if (File.Exists (lib))
68+
return lib;
69+
}
70+
71+
// 3. use PATH or default loading mechanism
72+
return libWithExt;
73+
}
74+
}
75+
76+
public static T GetSymbolDelegate<T> (IntPtr library, string name)
77+
where T : Delegate
78+
{
79+
var symbol = GetSymbol (library, name);
80+
if (symbol == IntPtr.Zero)
81+
throw new EntryPointNotFoundException ($"Unable to load symbol '{name}'.");
82+
83+
#if NET45
84+
return (T)Marshal.GetDelegateForFunctionPointer (symbol, typeof (T));
85+
#else
86+
return Marshal.GetDelegateForFunctionPointer<T> (symbol);
87+
#endif
88+
}
89+
90+
public static IntPtr LoadLibrary (string libraryName)
91+
{
92+
if (string.IsNullOrEmpty (libraryName))
93+
throw new ArgumentNullException (nameof (libraryName));
94+
95+
IntPtr handle;
96+
if (PlatformConfiguration.IsWindows)
97+
handle = Win32.LoadLibrary (libraryName);
98+
else if (PlatformConfiguration.IsLinux)
99+
handle = Linux.dlopen (libraryName);
100+
else if (PlatformConfiguration.IsMac)
101+
handle = Mac.dlopen (libraryName);
102+
else
103+
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to load library '{libraryName}'.");
104+
105+
return handle;
106+
}
107+
108+
public static IntPtr GetSymbol (IntPtr library, string symbolName)
109+
{
110+
if (string.IsNullOrEmpty (symbolName))
111+
throw new ArgumentNullException (nameof (symbolName));
112+
113+
IntPtr handle;
114+
if (PlatformConfiguration.IsWindows)
115+
handle = Win32.GetProcAddress (library, symbolName);
116+
else if (PlatformConfiguration.IsLinux)
117+
handle = Linux.dlsym (library, symbolName);
118+
else if (PlatformConfiguration.IsMac)
119+
handle = Mac.dlsym (library, symbolName);
120+
else
121+
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to load symbol '{symbolName}' from library {library}.");
122+
123+
return handle;
124+
}
125+
126+
public static void FreeLibrary (IntPtr library)
127+
{
128+
if (library == IntPtr.Zero)
129+
return;
130+
131+
if (PlatformConfiguration.IsWindows)
132+
Win32.FreeLibrary (library);
133+
else if (PlatformConfiguration.IsLinux)
134+
Linux.dlclose (library);
135+
else if (PlatformConfiguration.IsMac)
136+
Mac.dlclose (library);
137+
else
138+
throw new PlatformNotSupportedException ($"Current platform is unknown, unable to close library '{library}'.");
139+
}
140+
141+
#pragma warning disable IDE1006 // Naming Styles
142+
private static class Mac
143+
{
144+
private const string SystemLibrary = "/usr/lib/libSystem.dylib";
145+
146+
private const int RTLD_LAZY = 1;
147+
private const int RTLD_NOW = 2;
148+
149+
public static IntPtr dlopen (string path, bool lazy = true) =>
150+
dlopen (path, lazy ? RTLD_LAZY : RTLD_NOW);
151+
152+
[DllImport (SystemLibrary)]
153+
public static extern IntPtr dlopen (string path, int mode);
154+
155+
[DllImport (SystemLibrary)]
156+
public static extern IntPtr dlsym (IntPtr handle, string symbol);
157+
158+
[DllImport (SystemLibrary)]
159+
public static extern void dlclose (IntPtr handle);
160+
}
161+
162+
private static class Linux
163+
{
164+
private const string SystemLibrary = "libdl.so";
165+
166+
private const int RTLD_LAZY = 1;
167+
private const int RTLD_NOW = 2;
168+
169+
public static IntPtr dlopen (string path, bool lazy = true) =>
170+
dlopen (path, lazy ? RTLD_LAZY : RTLD_NOW);
171+
172+
[DllImport (SystemLibrary)]
173+
public static extern IntPtr dlopen (string path, int mode);
174+
175+
[DllImport (SystemLibrary)]
176+
public static extern IntPtr dlsym (IntPtr handle, string symbol);
177+
178+
[DllImport (SystemLibrary)]
179+
public static extern void dlclose (IntPtr handle);
180+
}
181+
182+
private static class Win32
183+
{
184+
private const string SystemLibrary = "Kernel32.dll";
185+
186+
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
187+
public static extern IntPtr LoadLibrary (string lpFileName);
188+
189+
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
190+
public static extern IntPtr GetProcAddress (IntPtr hModule, string lpProcName);
191+
192+
[DllImport (SystemLibrary, SetLastError = true, CharSet = CharSet.Ansi)]
193+
public static extern void FreeLibrary (IntPtr hModule);
194+
}
195+
#pragma warning restore IDE1006 // Naming Styles
196+
}
197+
#endif
198+
}
Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Runtime.InteropServices;
43

54
#if HARFBUZZ
@@ -11,20 +10,62 @@ namespace SkiaSharp
1110
internal static class PlatformConfiguration
1211
{
1312
public static bool IsUnix { get; }
13+
1414
public static bool IsWindows { get; }
1515

16+
public static bool IsMac { get; }
17+
18+
public static bool IsLinux { get; }
19+
1620
static PlatformConfiguration ()
1721
{
1822
#if WINDOWS_UWP
23+
IsMac = false;
24+
IsLinux = false;
1925
IsUnix = false;
2026
IsWindows = true;
21-
#elif NET_STANDARD
22-
IsUnix = RuntimeInformation.IsOSPlatform (OSPlatform.OSX) || RuntimeInformation.IsOSPlatform (OSPlatform.Linux);
23-
IsWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows);
24-
#else
27+
#elif NET45
2528
IsUnix = Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix;
2629
IsWindows = !IsUnix;
30+
IsMac = IsUnix && MacPlatformDetector.IsMac.Value;
31+
IsLinux = IsUnix && !IsMac;
32+
#else
33+
IsMac = RuntimeInformation.IsOSPlatform (OSPlatform.OSX);
34+
IsLinux = RuntimeInformation.IsOSPlatform (OSPlatform.Linux);
35+
IsUnix = IsMac || IsLinux;
36+
IsWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows);
2737
#endif
2838
}
39+
40+
#if NET45
41+
#pragma warning disable IDE1006 // Naming Styles
42+
private static class MacPlatformDetector
43+
{
44+
internal static readonly Lazy<bool> IsMac = new Lazy<bool> (IsRunningOnMac);
45+
46+
[DllImport ("libc")]
47+
static extern int uname (IntPtr buf);
48+
49+
static bool IsRunningOnMac ()
50+
{
51+
IntPtr buf = IntPtr.Zero;
52+
try {
53+
buf = Marshal.AllocHGlobal (8192);
54+
// This is a hacktastic way of getting sysname from uname ()
55+
if (uname (buf) == 0) {
56+
string os = Marshal.PtrToStringAnsi (buf);
57+
if (os == "Darwin")
58+
return true;
59+
}
60+
} catch {
61+
} finally {
62+
if (buf != IntPtr.Zero)
63+
Marshal.FreeHGlobal (buf);
64+
}
65+
return false;
66+
}
67+
}
68+
#pragma warning restore IDE1006 // Naming Styles
69+
#endif
2970
}
3071
}

binding/Binding/SKCanvas.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ public void Flush ()
793793

794794
public void DrawAnnotation (SKRect rect, string key, SKData value)
795795
{
796-
var bytes = StringUtilities.GetEncodedText (key, SKTextEncoding.Utf8);
796+
var bytes = StringUtilities.GetEncodedText (key, SKTextEncoding.Utf8, true);
797797
fixed (byte* b = bytes) {
798798
SkiaApi.sk_canvas_draw_annotation (base.Handle, &rect, b, value == null ? IntPtr.Zero : value.Handle);
799799
}

binding/Binding/SKData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static SKData Create (string filename)
8787
if (string.IsNullOrEmpty (filename))
8888
throw new ArgumentException ("The filename cannot be empty.", nameof (filename));
8989

90-
var utf8path = StringUtilities.GetEncodedText (filename, SKTextEncoding.Utf8);
90+
var utf8path = StringUtilities.GetEncodedText (filename, SKTextEncoding.Utf8, true);
9191
fixed (byte* u = utf8path) {
9292
return GetObject (SkiaApi.sk_data_new_from_file (u));
9393
}

binding/Binding/SKFontManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public SKTypeface CreateTypeface (string path, int index = 0)
9191
if (path == null)
9292
throw new ArgumentNullException (nameof (path));
9393

94-
var utf8path = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8);
94+
var utf8path = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8, true);
9595
fixed (byte* u = utf8path) {
9696
return SKTypeface.GetObject (SkiaApi.sk_fontmgr_create_from_file (Handle, u, index));
9797
}

binding/Binding/SKObject.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ internal ConcurrentDictionary<IntPtr, SKObject> KeepAliveObjects {
3636

3737
static SKObject ()
3838
{
39+
SkiaSharpVersion.CheckNativeLibraryCompatible (true);
40+
3941
SKColorSpace.EnsureStaticInstanceAreInitialized ();
4042
SKData.EnsureStaticInstanceAreInitialized ();
4143
SKFontManager.EnsureStaticInstanceAreInitialized ();

binding/Binding/SKStream.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ public SKFileStream (string path)
274274

275275
private static IntPtr CreateNew (string path)
276276
{
277-
var bytes = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8);
277+
var bytes = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8, true);
278278
fixed (byte* p = bytes) {
279279
return SkiaApi.sk_filestream_new (p);
280280
}
@@ -487,7 +487,7 @@ public SKFileWStream (string path)
487487

488488
private static IntPtr CreateNew (string path)
489489
{
490-
var bytes = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8);
490+
var bytes = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8, true);
491491
fixed (byte* p = bytes) {
492492
return SkiaApi.sk_filewstream_new (p);
493493
}

binding/Binding/SKTypeface.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public static SKTypeface FromFile (string path, int index = 0)
107107
if (path == null)
108108
throw new ArgumentNullException (nameof (path));
109109

110-
var utf8path = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8);
110+
var utf8path = StringUtilities.GetEncodedText (path, SKTextEncoding.Utf8, true);
111111
fixed (byte* u = utf8path) {
112112
return GetObject (SkiaApi.sk_typeface_create_from_file (u, index));
113113
}

binding/Binding/SkiaApi.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace SkiaSharp
1+
using System;
2+
3+
namespace SkiaSharp
24
{
35
internal partial class SkiaApi
46
{
@@ -12,14 +14,20 @@ internal partial class SkiaApi
1214
private const string SKIA = "libSkiaSharp.so";
1315
#elif __MACOS__
1416
private const string SKIA = "libSkiaSharp.dylib";
15-
#elif __DESKTOP__
16-
private const string SKIA = "libSkiaSharp";
1717
#elif WINDOWS_UWP
1818
private const string SKIA = "libSkiaSharp.dll";
19-
#elif NET_STANDARD
20-
private const string SKIA = "libSkiaSharp";
19+
#elif __TIZEN__
20+
private const string SKIA = "libSkiaSharp.so";
2121
#else
2222
private const string SKIA = "libSkiaSharp";
2323
#endif
24+
25+
#if USE_DELEGATES
26+
private static readonly Lazy<IntPtr> libSkiaSharpHandle =
27+
new Lazy<IntPtr> (() => LibraryLoader.LoadLocalLibrary<SkiaApi> (SKIA));
28+
29+
private static T GetSymbol<T> (string name) where T : Delegate =>
30+
LibraryLoader.GetSymbolDelegate<T> (libSkiaSharpHandle.Value, name);
31+
#endif
2432
}
2533
}

0 commit comments

Comments
 (0)