diff --git a/CHANGELOG.md b/CHANGELOG.md index dd861900..089d6905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to NetVips will be documented in this file. See [here](CHANG The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [3.0.0] - TBD +### Added +- Add support for a single shared libvips binary on Windows ([#211](https://github.com/kleisauke/net-vips/issues/211)). + ### Removed - Drop support for .NET Standard 2.0 and Mono. NetVips now targets .NET 6 (`net6.0`) and .NET Framework 4.5.2 (`net452`) moving forward. See https://devblogs.microsoft.com/dotnet/the-future-of-net-standard/ for more information. diff --git a/build/Build.cs b/build/Build.cs index 5f127d12..6d8ffd29 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -132,21 +132,10 @@ protected override void OnBuildInitialized() .DependsOn(Clean) .Executes(() => { - // Need to build the macOS and *nix DLL first. - DotNetBuild(c => c - .SetProjectFile(Solution.NetVips) - .SetConfiguration(Configuration) - .SetFramework("netstandard2.0") - .AddProperty("Platform", "AnyCPU") - .CombineWith( - new[] { "OSX", "Unix" }, - (_, os) => _.AddProperty("TargetOS", os))); - DotNetPack(c => c .SetProject(Solution.NetVips) .SetConfiguration(Configuration) .SetOutputDirectory(ArtifactsDirectory) - .AddProperty("TargetOS", "Windows") ); }); diff --git a/src/NetVips/Interop/Libraries.OSX.cs b/src/NetVips/Interop/Libraries.OSX.cs deleted file mode 100644 index 572edcf2..00000000 --- a/src/NetVips/Interop/Libraries.OSX.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NetVips.Interop -{ - internal static partial class Libraries - { - // We can safely define all these variables as `libvips.42.dylib` since - // DLLImport uses dlsym() on macOS. This function also searches for named - // symbols in the dependencies of the shared library. Therefore, we can - // provide libvips as a single shared library with all dependencies - // statically linked without breaking compatibility with shared builds - // (i.e. what is usually installed via package managers). - internal const string GLib = "libvips.42.dylib", - GObject = "libvips.42.dylib", - Vips = "libvips.42.dylib"; - } -} \ No newline at end of file diff --git a/src/NetVips/Interop/Libraries.Unix.cs b/src/NetVips/Interop/Libraries.Unix.cs deleted file mode 100644 index 93d2e274..00000000 --- a/src/NetVips/Interop/Libraries.Unix.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NetVips.Interop -{ - internal static partial class Libraries - { - // We can safely define all these variables as `libvips.so.42` since - // DLLImport uses dlsym() on *nix. This function also searches for named - // symbols in the dependencies of the shared library. Therefore, we can - // provide libvips as a single shared library with all dependencies - // statically linked without breaking compatibility with shared builds - // (i.e. what is usually installed via package managers). - internal const string GLib = "libvips.so.42", - GObject = "libvips.so.42", - Vips = "libvips.so.42"; - } -} \ No newline at end of file diff --git a/src/NetVips/Interop/Libraries.Windows.cs b/src/NetVips/Interop/Libraries.Windows.cs deleted file mode 100644 index 002daed4..00000000 --- a/src/NetVips/Interop/Libraries.Windows.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NetVips.Interop -{ - internal static partial class Libraries - { - // We cannot define all these variables as `libvips-42.dll` without - // breaking compatibility with the shared Windows build. Therefore, - // we always ship at least 3 DLLs. - internal const string GLib = "libglib-2.0-0.dll", - GObject = "libgobject-2.0-0.dll", - Vips = "libvips-42.dll"; - } -} \ No newline at end of file diff --git a/src/NetVips/Interop/Libraries.cs b/src/NetVips/Interop/Libraries.cs new file mode 100644 index 00000000..dcb80b4a --- /dev/null +++ b/src/NetVips/Interop/Libraries.cs @@ -0,0 +1,13 @@ +namespace NetVips.Interop +{ + internal static class Libraries + { + /// + /// These library names are remapped in a cross-platform manner, + /// . + /// + internal const string GLib = "libglib-2.0-0.dll", + GObject = "libgobject-2.0-0.dll", + Vips = "libvips-42.dll"; + } +} \ No newline at end of file diff --git a/src/NetVips/ModuleInitializer.cs b/src/NetVips/ModuleInitializer.cs index 1f9e971b..e428d4c7 100644 --- a/src/NetVips/ModuleInitializer.cs +++ b/src/NetVips/ModuleInitializer.cs @@ -1,6 +1,10 @@ namespace NetVips { using System; + using System.Reflection; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Interop; /// /// All code inside the method is ran as soon as the assembly is loaded. @@ -22,6 +26,63 @@ public static class ModuleInitializer /// public static int? Version; +#if NET6_0_OR_GREATER + /// + /// Windows specific: is GLib statically-linked in `libvips-42.dll`? + /// + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + private static bool _gLibStaticallyLinked = true; + + /// + /// A cache for . + /// + internal static readonly Dictionary DllImportCache = + new Dictionary(); + + internal static string RemapLibraryName(string libraryName) + { + // For Windows, we try to locate the GLib symbols within + // `libvips-42.dll` first. If these symbols cannot be found there, + // we proceed to locate them within `libglib-2.0-0.dll` and + // `libgobject-2.0-0.dll`. Note that distributing a single shared + // library is only possible when we drop support for .NET Framework. + // Therefore, we always ship at least 3 DLLs for now. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return _gLibStaticallyLinked ? Libraries.Vips : libraryName; + } + + // We can safely remap the library names to `libvips.so.42` on *nix + // and `libvips.42.dylib` on macOS since DLLImport uses dlsym() there. + // This function also searches for named symbols in the dependencies + // of the shared library. Therefore, we can provide libvips as a + // single shared library with all dependencies statically linked + // without breaking compatibility with the shared builds + // (i.e. what is usually installed via package managers). + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? "libvips.42.dylib" + : "libvips.so.42"; + } + + internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + libraryName = RemapLibraryName(libraryName); + if (DllImportCache.TryGetValue(libraryName, out var cachedHandle)) + { + return cachedHandle; + } + + if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out var handle)) + { + DllImportCache[libraryName] = handle; + return handle; + } + + // Fallback to the default import resolver. + return IntPtr.Zero; + } +#endif + /// /// Initializes the module. /// @@ -30,6 +91,10 @@ public static class ModuleInitializer #pragma warning restore CA2255 public static void Initialize() { +#if NET6_0_OR_GREATER + NativeLibrary.SetDllImportResolver(typeof(ModuleInitializer).Assembly, DllImportResolver); +#endif + try { VipsInitialized = NetVips.Init(); @@ -38,6 +103,19 @@ public static void Initialize() Version = NetVips.Version(0, false); Version = (Version << 8) + NetVips.Version(1, false); Version = (Version << 8) + NetVips.Version(2, false); + +#if NET6_0_OR_GREATER + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + try + { + _gLibStaticallyLinked = NetVips.TypeFromName("VipsImage") != IntPtr.Zero; + } + catch + { + _gLibStaticallyLinked = false; + } +#endif } else { diff --git a/src/NetVips/NetVips.csproj b/src/NetVips/NetVips.csproj index 4753f587..d2c763e9 100644 --- a/src/NetVips/NetVips.csproj +++ b/src/NetVips/NetVips.csproj @@ -11,30 +11,12 @@ x64;x86;ARM64;ARM32 true - $(DefaultItemExcludes);Interop\Libraries.*.cs false - false true true - - - Unix - OSX - Windows - - - - - $(MSBuildThisFileDirectory)bin\$(Platform)\$(Configuration)\$(TargetFramework)\$(TargetOS) - $(BaseIntermediateOutputPath)\$(Platform)\$(Configuration)\$(TargetFramework)\$(TargetOS)\ - - - - Interop\Libraries.cs - @@ -43,16 +25,4 @@ - - - - true - runtimes\unix\lib\netstandard2.0 - - - true - runtimes\osx\lib\netstandard2.0 - - - \ No newline at end of file