Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dynamic DLL resolution of NativeShims for .NET 4.7 x86/x64 architectures #154

Merged
merged 3 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions Yubico.Core/src/Yubico/PlatformInterop/Libraries.Net47.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2024 Yubico AB
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#if NET47
using System;
using System.IO;

namespace Yubico.PlatformInterop
{
/// <summary>
/// .NET Framework 4.7 specific implementation for native library management.
/// </summary>
internal static partial class Libraries
{
/// <summary>
/// Encapsulates the .NET Framework 4.7 specific implementation details for native library management.
/// This nested class handles the dynamic loading of architecture-specific (x86/x64) native libraries.
/// </summary>
private static class Net47Implementation
{
// Handle to the loaded native library
private static UnmanagedDynamicLibrary? _nativeShims;

/// <summary>
/// Gets the full path to the architecture-specific native library.
/// </summary>
/// <remarks>
/// The path is constructed based on:
/// - The application's base directory
/// - The current process architecture (x86/x64)
/// - The native library filename
/// </remarks>
private static string NativeShimsPath =>
Path.Combine(
AppDomain.CurrentDomain.BaseDirectory,
Environment.Is64BitProcess
? "x64"
: "x86",
NativeShims);

/// <summary>
/// Initializes the native library for the current architecture.
/// </summary>
/// <remarks>
/// This method is called by the public EnsureInitialized method.
/// It ensures the appropriate version (x86/x64) of the native library is loaded.
/// </remarks>
internal static void Initialize()
{
try
{
EnsureNativeShimsLoaded();
}
catch (Exception ex)
{
throw new DllNotFoundException(
$"Failed to load native library from {NativeShimsPath}. " +
$"Ensure the correct {(Environment.Is64BitProcess ? "x64" : "x86")} version is present.",
ex);
}
}

/// <summary>
/// Loads the native library if it hasn't been loaded already.
/// </summary>
/// <exception cref="Exception">
/// A variety of exceptions can be thrown during library loading, including but not limited to:
/// - FileNotFoundException: If the DLL file is not found
/// - BadImageFormatException: If the DLL is not compatible with the current architecture
/// - Other exceptions based on the specific error condition encountered during loading
/// </exception>
private static void EnsureNativeShimsLoaded()
{
if (_nativeShims != null)
{
return;
}

_nativeShims = UnmanagedDynamicLibrary.Open(NativeShimsPath);
}
}
}
}
#endif
61 changes: 61 additions & 0 deletions Yubico.Core/src/Yubico/PlatformInterop/Libraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,75 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

namespace Yubico.PlatformInterop
{
/// <summary>
/// Handles the loading and management of native libraries required by the Yubico SDK.
/// </summary>
/// <remarks>
/// This class provides cross-platform and cross-framework support for loading native libraries.
/// The implementation differs based on the target framework:
///
/// For .NET Framework 4.7:
/// - Native libraries must be placed in architecture-specific subdirectories (x86/x64)
/// - Library loading is handled explicitly at runtime based on process architecture
/// - Requires proper cleanup through the Cleanup method
///
/// For Modern .NET:
/// - Native libraries are handled by the runtime's built-in library loading mechanism
/// - Architecture-specific loading is managed automatically
/// - No explicit cleanup is required
/// </remarks>
internal static partial class Libraries
{
#if NET47
/// <summary>
/// The filename of the native shims library for .NET Framework 4.7.
/// </summary>
/// <remarks>
/// For .NET Framework 4.7, the DLL must be placed in an architecture-specific subdirectory:
/// - x86/Yubico.NativeShims.dll for 32-bit processes
/// - x64/Yubico.NativeShims.dll for 64-bit processes
/// The correct version is loaded at runtime based on the process architecture.
/// </remarks>
internal const string NativeShims = "Yubico.NativeShims.dll";

/// <summary>
/// Ensures the native library is properly loaded for .NET Framework 4.7.
/// </summary>
/// <exception cref="DllNotFoundException">
/// Thrown when the native library cannot be loaded. This could be due to:
/// - Missing DLL file in the architecture-specific directory (x86/x64)
/// - Incorrect architecture (x86/x64 mismatch)
/// - Missing dependencies
/// - Insufficient permissions
/// </exception>
/// <remarks>
/// This method must be called before any P/Invoke calls are made.
/// The implementation details are handled in Libraries.Net47.cs.
/// </remarks>
public static void EnsureInitialized() => Net47Implementation.Initialize();
#else
DennisDyallo marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// The filename of the native shims library for modern .NET versions.
/// </summary>
/// <remarks>
/// For modern .NET, the runtime automatically handles library loading and architecture selection.
/// The DLL extension is omitted as it's platform-specific and managed by the runtime.
/// The library should be properly packaged with the correct runtimes/* folder structure in the NuGet package.
/// </remarks>
internal const string NativeShims = "Yubico.NativeShims";

/// <summary>
/// No-op implementation for modern .NET versions.
/// </summary>
/// <remarks>
/// Library loading is handled automatically by the runtime.
/// This method exists only for API compatibility with .NET Framework 4.7 code.
/// </remarks>
public static void EnsureInitialized() { }
#endif
}
}
5 changes: 5 additions & 0 deletions Yubico.Core/src/Yubico/PlatformInterop/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ internal static partial class NativeMethods
private const string MacDlLib = "libdl.dylib";
private const string LinuxDlLib = "libdl.so";

static NativeMethods()
{
Libraries.EnsureInitialized();
}

DennisDyallo marked this conversation as resolved.
Show resolved Hide resolved
// Note that the DefaultDllImportSearchPaths attribute is a security best
// practice on the Windows platform (and required by our analyzer
// settings). It does not currently have any effect on platforms other
Expand Down
14 changes: 14 additions & 0 deletions Yubico.NativeShims/msbuild/Yubico.NativeShims.targets
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,18 @@
<Visible>false</Visible>
</Content>
</ItemGroup>

<!-- AnyCPU -->
<ItemGroup Condition="'$(Platform)' == 'AnyCPU'">
<Content Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x86\native\Yubico.NativeShims.dll">
<Link>x86\Yubico.NativeShims.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
<Content Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\Yubico.NativeShims.dll">
<Link>x64\Yubico.NativeShims.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>false</Visible>
</Content>
</ItemGroup>
</Project>
Loading