Skip to content

Commit 7cda786

Browse files
committed
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)
1 parent c23eab0 commit 7cda786

File tree

11 files changed

+8998
-106
lines changed

11 files changed

+8998
-106
lines changed
+198
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+
path = Path.Combine (path, arch, libWithExt);
50+
if (File.Exists (path))
51+
return path;
52+
// 1.2 in root
53+
path = Path.Combine (path, libWithExt);
54+
if (File.Exists (path))
55+
return path;
56+
}
57+
58+
// 2. try current directory
59+
path = Directory.GetCurrentDirectory ();
60+
if (!string.IsNullOrEmpty (path)) {
61+
// 2.1 in platform sub dir
62+
path = Path.Combine (path, arch, libWithExt);
63+
if (File.Exists (path))
64+
return path;
65+
// 2.2 in root
66+
path = Path.Combine (path, libWithExt);
67+
if (File.Exists (path))
68+
return path;
69+
}
70+
71+
// 3. use PATH or default loading mechanism
72+
return libraryName;
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 __NET_45__
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+
}

binding/Binding.Shared/PlatformConfiguration.cs

+44-3
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);
27+
#elif NET_STANDARD || __NET_46__
28+
IsMac = RuntimeInformation.IsOSPlatform (OSPlatform.OSX);
29+
IsLinux = RuntimeInformation.IsOSPlatform (OSPlatform.Linux);
30+
IsUnix = IsMac || IsLinux;
2331
IsWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows);
2432
#else
2533
IsUnix = Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix;
2634
IsWindows = !IsUnix;
35+
IsMac = IsUnix && MacPlatformDetector.IsMac.Value;
36+
IsLinux = IsUnix && !IsMac;
2737
#endif
2838
}
39+
40+
#if !NET_STANDARD && !__NET_46__
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/SkiaApi.cs

+16-1
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
{
@@ -21,5 +23,18 @@ internal partial class SkiaApi
2123
#else
2224
private const string SKIA = "libSkiaSharp";
2325
#endif
26+
27+
#if USE_DELEGATES
28+
private static IntPtr libSkiaSharpHandle;
29+
30+
private static T Get<T> (string name)
31+
where T : Delegate
32+
{
33+
if (libSkiaSharpHandle == IntPtr.Zero)
34+
libSkiaSharpHandle = LibraryLoader.LoadLocalLibrary<SkiaApi> (SKIA);
35+
36+
return LibraryLoader.GetSymbolDelegate<T> (libSkiaSharpHandle, name);
37+
}
38+
#endif
2439
}
2540
}

0 commit comments

Comments
 (0)