Skip to content

Commit

Permalink
Add support for a single shared libvips binary on Windows (#211)
Browse files Browse the repository at this point in the history
When targeting .NET 6.0 or higher.
  • Loading branch information
kleisauke committed Oct 14, 2024
1 parent 2f4f658 commit 1448040
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 83 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 0 additions & 11 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
);
});

Expand Down
15 changes: 0 additions & 15 deletions src/NetVips/Interop/Libraries.OSX.cs

This file was deleted.

15 changes: 0 additions & 15 deletions src/NetVips/Interop/Libraries.Unix.cs

This file was deleted.

12 changes: 0 additions & 12 deletions src/NetVips/Interop/Libraries.Windows.cs

This file was deleted.

13 changes: 13 additions & 0 deletions src/NetVips/Interop/Libraries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace NetVips.Interop
{
internal static class Libraries
{
/// <remarks>
/// These library names are remapped in a cross-platform manner,
/// <see cref="ModuleInitializer.Initialize"/>.
/// </remarks>
internal const string GLib = "libglib-2.0-0.dll",
GObject = "libgobject-2.0-0.dll",
Vips = "libvips-42.dll";
}
}
78 changes: 78 additions & 0 deletions src/NetVips/ModuleInitializer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
namespace NetVips
{
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Interop;

/// <summary>
/// All code inside the <see cref="Initialize"/> method is ran as soon as the assembly is loaded.
Expand All @@ -22,6 +26,63 @@ public static class ModuleInitializer
/// </summary>
public static int? Version;

#if NET6_0_OR_GREATER
/// <summary>
/// Windows specific: is GLib statically-linked in `libvips-42.dll`?
/// </summary>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
private static bool _gLibStaticallyLinked = true;

/// <summary>
/// A cache for <see cref="DllImportResolver"/>.
/// </summary>
internal static readonly Dictionary<string, IntPtr> DllImportCache =
new Dictionary<string, IntPtr>();

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

/// <summary>
/// Initializes the module.
/// </summary>
Expand All @@ -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();
Expand All @@ -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
{
Expand Down
30 changes: 0 additions & 30 deletions src/NetVips/NetVips.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,12 @@
<StartupObject />
<Platforms>x64;x86;ARM64;ARM32</Platforms>
<Optimize>true</Optimize>
<DefaultItemExcludes>$(DefaultItemExcludes);Interop\Libraries.*.cs</DefaultItemExcludes>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>

<!-- Give an initial value based on the operating system where it's currently running on. -->
<PropertyGroup Condition="'$(TargetOS)' == ''">
<TargetOS Condition="$([MSBuild]::IsOSPlatform('Linux')) OR $([MSBuild]::IsOSPlatform('FreeBSD'))">Unix</TargetOS>
<TargetOS Condition="$([MSBuild]::IsOSPlatform('OSX'))">OSX</TargetOS>
<TargetOS Condition="$([MSBuild]::IsOSPlatform('Windows'))">Windows</TargetOS>
</PropertyGroup>

<!-- Append target operating system to output path -->
<PropertyGroup>
<OutputPath>$(MSBuildThisFileDirectory)bin\$(Platform)\$(Configuration)\$(TargetFramework)\$(TargetOS)</OutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)\$(Platform)\$(Configuration)\$(TargetFramework)\$(TargetOS)\</IntermediateOutputPath>
</PropertyGroup>

<ItemGroup>
<Compile Include="Interop\Libraries.$(TargetOS).cs">
<Link>Interop\Libraries.cs</Link>
</Compile>
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
<InternalsVisibleTo Include="$(AssemblyName).Samples" />
</ItemGroup>
Expand All @@ -43,16 +25,4 @@
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>

<!-- Include .NET Standard packages on *nix and macOS -->
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)bin\$(Platform)\$(Configuration)\netstandard2.0\Unix\$(AssemblyName).dll">
<Pack>true</Pack>
<PackagePath>runtimes\unix\lib\netstandard2.0</PackagePath>
</None>
<None Include="$(MSBuildThisFileDirectory)bin\$(Platform)\$(Configuration)\netstandard2.0\OSX\$(AssemblyName).dll">
<Pack>true</Pack>
<PackagePath>runtimes\osx\lib\netstandard2.0</PackagePath>
</None>
</ItemGroup>

</Project>

0 comments on commit 1448040

Please sign in to comment.