From 5299aace1f8daf0b1465a649187023d8677ca9ac Mon Sep 17 00:00:00 2001 From: Rinat Abdullin Date: Fri, 27 Apr 2018 20:12:15 +0500 Subject: [PATCH 1/4] loading: on non-windows use libdl to load libfdb_c --- .../Native/UnmanagedLibrary.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/FoundationDB.Client/Native/UnmanagedLibrary.cs b/FoundationDB.Client/Native/UnmanagedLibrary.cs index 62659bbdd..681e95e67 100644 --- a/FoundationDB.Client/Native/UnmanagedLibrary.cs +++ b/FoundationDB.Client/Native/UnmanagedLibrary.cs @@ -72,34 +72,32 @@ protected override bool ReleaseHandle() [SuppressUnmanagedCodeSecurity] private static class NativeMethods { -#if __MonoCS__ - const string KERNEL = "dl"; + const string LIBDL = "dl"; + - [DllImport(KERNEL)] + [DllImport(LIBDL)] public static extern SafeLibraryHandle dlopen(string fileName, int flags); - [DllImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern int dlclose(IntPtr hModule); +#if __MonoCS__ - public static SafeLibraryHandle LoadLibrary(string fileName) + public static SafeLibraryHandle LoadPlatformLibrary(string fileName) { - return dlopen(fileName, 1); - } - public static bool FreeLibrary(IntPtr hModule) { return dlclose(hModule) == 0; } - #else const string KERNEL = "kernel32"; [DllImport(KERNEL, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] public static extern SafeLibraryHandle LoadLibrary(string fileName); - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - [DllImport(KERNEL, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FreeLibrary(IntPtr hModule); + public static SafeLibraryHandle LoadPlatformLibrary(string fileName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return LoadLibrary(fileName); + } + return dlopen(fileName, 1); + } #endif } @@ -112,7 +110,7 @@ public static UnmanagedLibrary Load(string path) { if (path == null) throw new ArgumentNullException("path"); - var handle = NativeMethods.LoadLibrary(path); + var handle = NativeMethods.LoadPlatformLibrary(path); if (handle == null || handle.IsInvalid) { var ex = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()); From 26667b0848051a8725fe36221a93e09e7ff693a0 Mon Sep 17 00:00:00 2001 From: Rinat Abdullin Date: Fri, 27 Apr 2018 22:23:12 +0500 Subject: [PATCH 2/4] Add platform-specific library closing methods Although FreeLibrary and dlclose aren't used right now, they will be needed later after #54 is resolved --- .../Native/UnmanagedLibrary.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/FoundationDB.Client/Native/UnmanagedLibrary.cs b/FoundationDB.Client/Native/UnmanagedLibrary.cs index 681e95e67..70934b092 100644 --- a/FoundationDB.Client/Native/UnmanagedLibrary.cs +++ b/FoundationDB.Client/Native/UnmanagedLibrary.cs @@ -78,18 +78,30 @@ private static class NativeMethods [DllImport(LIBDL)] public static extern SafeLibraryHandle dlopen(string fileName, int flags); + + [DllImport(LIBDL, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern int dlclose(IntPtr hModule); + #if __MonoCS__ public static SafeLibraryHandle LoadPlatformLibrary(string fileName) { return dlopen(fileName, 1); } + public static bool FreePlatformLibrary(IntPtr hModule) { return dlclose(hModule) == 0; } + #else const string KERNEL = "kernel32"; [DllImport(KERNEL, CharSet = CharSet.Auto, BestFitMapping = false, SetLastError = true)] public static extern SafeLibraryHandle LoadLibrary(string fileName); + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [DllImport(KERNEL, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FreeLibrary(IntPtr hModule); + public static SafeLibraryHandle LoadPlatformLibrary(string fileName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -98,6 +110,15 @@ public static SafeLibraryHandle LoadPlatformLibrary(string fileName) } return dlopen(fileName, 1); } + + public static bool FreePlatformLibrary(IntPtr hModule) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return FreeLibrary(hModule); + } + return return dlclose(hModule) == 0; + } #endif } From 3baba6ce67f0e2a61ec3e2b4d99ee1960f87e372 Mon Sep 17 00:00:00 2001 From: Rinat Abdullin Date: Fri, 27 Apr 2018 23:45:06 +0500 Subject: [PATCH 3/4] clean-up static FdbNative ctor to support osx and linux --- FoundationDB.Client/Native/FdbNative.cs | 59 +++++++++++-------- .../Native/UnmanagedLibrary.cs | 2 +- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/FoundationDB.Client/Native/FdbNative.cs b/FoundationDB.Client/Native/FdbNative.cs index 460032f0f..679dff78c 100644 --- a/FoundationDB.Client/Native/FdbNative.cs +++ b/FoundationDB.Client/Native/FdbNative.cs @@ -49,7 +49,7 @@ internal static unsafe class FdbNative private const string FDB_C_DLL = "libfdb_c.so"; #else /// Name of the C API dll used for P/Invoking - private const string FDB_C_DLL = "fdb_c.dll"; + private const string FDB_C_DLL = "fdb_c"; #endif /// Handle on the native FDB C API library @@ -237,40 +237,49 @@ static FdbNative() { // Impact of NativeLibPath: // - If null, don't preload the library, and let the CLR find the file using the default P/Invoke behavior - // - If String.Empty, call win32 LoadLibrary("fdb_c.dll") and let the os find the file (using the standard OS behavior) - // - Else, combine the path with "fdb_c.dll" and call LoadLibrary with the resulting (relative or absolute) path + // - If String.Empty, call win32 LoadLibrary(FDB_C_DLL) and let the os find the file (using the standard OS behavior) + // - If path is folder, append the FDB_C_DLL + // Afterwards - call LoadLibrary with the resulting (relative or absolute) path var libraryPath = Fdb.Options.NativeLibPath; - if (libraryPath != null) + + if (libraryPath == null) + { + return; + } + + try { - try + if (libraryPath.Length == 0) + { // CLR will handle the search + libraryPath = FDB_C_DLL; + } + else { - if (libraryPath.Length == 0) - { // CLR will handle the search - libraryPath = FDB_C_DLL; - } - else if (!libraryPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - { // add the file name - libraryPath = Path.Combine(Fdb.Options.NativeLibPath, FDB_C_DLL); + var fileName = Path.GetFileName(libraryPath); + if (String.IsNullOrEmpty(fileName)) + { + libraryPath = Path.Combine(libraryPath, FDB_C_DLL); } + } - FdbCLib = UnmanagedLibrary.Load(libraryPath); + FdbCLib = UnmanagedLibrary.Load(libraryPath); + } + catch (Exception e) + { + if (FdbCLib != null) FdbCLib.Dispose(); + FdbCLib = null; + if (e is BadImageFormatException && IntPtr.Size == 4) + { + e = new InvalidOperationException("The native FDB client is 64-bit only, and cannot be loaded in a 32-bit process.", e); } - catch (Exception e) + else { - if (FdbCLib != null) FdbCLib.Dispose(); - FdbCLib = null; - if (e is BadImageFormatException && IntPtr.Size == 4) - { - e = new InvalidOperationException("The native FDB client is 64-bit only, and cannot be loaded in a 32-bit process.", e); - } - else - { - e = new InvalidOperationException("An error occurred while loading the native FoundationDB library", e); - } - LibraryLoadError = ExceptionDispatchInfo.Capture(e); + e = new InvalidOperationException("An error occurred while loading the native FoundationDB library", e); } + LibraryLoadError = ExceptionDispatchInfo.Capture(e); } + } private static void EnsureLibraryIsLoaded() diff --git a/FoundationDB.Client/Native/UnmanagedLibrary.cs b/FoundationDB.Client/Native/UnmanagedLibrary.cs index 70934b092..968045b0a 100644 --- a/FoundationDB.Client/Native/UnmanagedLibrary.cs +++ b/FoundationDB.Client/Native/UnmanagedLibrary.cs @@ -117,7 +117,7 @@ public static bool FreePlatformLibrary(IntPtr hModule) { return FreeLibrary(hModule); } - return return dlclose(hModule) == 0; + return dlclose(hModule) == 0; } #endif } From 1cbc0733e0088e69d935b68ea0f10ed83f902765 Mon Sep 17 00:00:00 2001 From: Rinat Abdullin Date: Sat, 28 Apr 2018 10:40:34 +0500 Subject: [PATCH 4/4] Simplify library loading on non-windows We keep existing behavior as is for the windows platform and simplify non-windows loading. On non-win we load the native library only if explicitly provided --- FoundationDB.Client/Native/FdbNative.cs | 67 ++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/FoundationDB.Client/Native/FdbNative.cs b/FoundationDB.Client/Native/FdbNative.cs index 679dff78c..bf4adbc06 100644 --- a/FoundationDB.Client/Native/FdbNative.cs +++ b/FoundationDB.Client/Native/FdbNative.cs @@ -235,34 +235,15 @@ public static extern void fdb_transaction_clear_range( static FdbNative() { - // Impact of NativeLibPath: - // - If null, don't preload the library, and let the CLR find the file using the default P/Invoke behavior - // - If String.Empty, call win32 LoadLibrary(FDB_C_DLL) and let the os find the file (using the standard OS behavior) - // - If path is folder, append the FDB_C_DLL - // Afterwards - call LoadLibrary with the resulting (relative or absolute) path - - var libraryPath = Fdb.Options.NativeLibPath; + var libraryPath = GetPreloadPath(); if (libraryPath == null) - { + { // PInvoke will load return; } - + try { - if (libraryPath.Length == 0) - { // CLR will handle the search - libraryPath = FDB_C_DLL; - } - else - { - var fileName = Path.GetFileName(libraryPath); - if (String.IsNullOrEmpty(fileName)) - { - libraryPath = Path.Combine(libraryPath, FDB_C_DLL); - } - } - FdbCLib = UnmanagedLibrary.Load(libraryPath); } catch (Exception e) @@ -275,13 +256,53 @@ static FdbNative() } else { - e = new InvalidOperationException("An error occurred while loading the native FoundationDB library", e); + e = new InvalidOperationException($"An error occurred while loading the native FoundationDB library: '{libraryPath}'.", e); } LibraryLoadError = ExceptionDispatchInfo.Capture(e); } } + private static string GetPreloadPath() + { + // we need to provide sensible defaults for loading the native library + // if this method returns null we'll let PInvoke deal + // otherwise - use explicit platform-specific dll loading + var libraryPath = Fdb.Options.NativeLibPath; + + // on non-windows, library loading by convention just works. + // unless override is provided, just let PInvoke do the work + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (string.IsNullOrEmpty(libraryPath)) + { + return null; + } + // otherwise just use the provided path + return libraryPath; + } + + // Impact of NativeLibPath on windows: + // - If null, don't preload the library, and let the CLR find the file using the default P/Invoke behavior + // - If String.Empty, call win32 LoadLibrary(FDB_C_DLL + ".dll") and let the os find the file (using the standard OS behavior) + // - If path is folder, append the FDB_C_DLL + var winDllWithExtension = FDB_C_DLL + ".dll"; + if (libraryPath == null) + { + return null; + } + if (libraryPath.Length == 0) + { + return winDllWithExtension; + } + var fileName = Path.GetFileName(libraryPath); + if (String.IsNullOrEmpty(fileName)) + { + libraryPath = Path.Combine(libraryPath, winDllWithExtension); + } + return libraryPath; + } + private static void EnsureLibraryIsLoaded() { // should be inlined