/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Compression
* File: LibraryWrapper.cs
*
* LibraryWrapper.cs is part of VNLib.Net.Compression which is part of
* the larger VNLib collection of libraries and utilities.
*
* VNLib.Net.Compression is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Net.Compression is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VNLib.Net.Compression. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using VNLib.Utils.Native;
using VNLib.Utils.Extensions;
using VNLib.Net.Http;
namespace VNLib.Net.Compression
{
/*
* Configure the delegate methods for the native library
*
* All calling conventions are set to Cdecl because the native
* library is compiled with Cdecl on all platforms.
*/
[SafeMethodName("GetSupportedCompressors")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate CompressionMethod GetSupportedMethodsDelegate();
[SafeMethodName("GetCompressorBlockSize")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate long GetBlockSizeDelegate(IntPtr compressor);
[SafeMethodName("GetCompressorType")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate CompressionMethod GetCompressorTypeDelegate(IntPtr compressor);
[SafeMethodName("GetCompressorLevel")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate CompressionLevel GetCompressorLevelDelegate(IntPtr compressor);
[SafeMethodName("AllocateCompressor")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate IntPtr AllocateCompressorDelegate(CompressionMethod type, CompressionLevel level);
[SafeMethodName("FreeCompressor")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int FreeCompressorDelegate(IntPtr compressor);
[SafeMethodName("GetCompressedSize")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate long GetCompressedSizeDelegate(IntPtr compressor, ulong uncompressedSize, int flush);
[SafeMethodName("CompressBlock")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe delegate int CompressBlockDelegate(IntPtr compressor, CompressionOperation* operation);
///
///
/// Represents a wrapper that provides access to the native compression library
/// specified by a file path.
///
///
/// NOTE: This library is not meant to be freed, its meant to be loaded at runtime
/// and used for the lifetime of the application.
///
///
internal sealed class LibraryWrapper : IDisposable
{
private readonly SafeLibraryHandle _lib;
private MethodTable _methodTable;
public string LibFilePath { get; }
private LibraryWrapper(SafeLibraryHandle lib, string path, in MethodTable methodTable)
{
_lib = lib;
_methodTable = methodTable;
LibFilePath = path;
}
///
/// Loads the native library at the specified path into the current process
///
/// The path to the native library to load
///
/// The native library wrapper
public static LibraryWrapper LoadLibrary(string filePath, DllImportSearchPath searchType)
{
//Load the library into the current process
SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(filePath, searchType);
try
{
//build the method table
MethodTable methods = new()
{
GetMethods = lib.DangerousGetFunction(),
GetBlockSize = lib.DangerousGetFunction(),
GetCompType = lib.DangerousGetFunction(),
GetCompLevel = lib.DangerousGetFunction(),
Alloc = lib.DangerousGetFunction(),
Free = lib.DangerousGetFunction(),
GetOutputSize = lib.DangerousGetFunction(),
Compress = lib.DangerousGetFunction()
};
return new (lib, filePath, in methods);
}
catch
{
lib.Dispose();
throw;
}
}
///
/// Gets an enum value of the supported compression methods by the underlying library
///
/// The supported compression methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CompressionMethod GetSupportedMethods() => _methodTable.GetMethods();
/*
* Block size is stored as a uint32 in the native library
* compressor struct
*/
///
/// Gets the block size of the specified compressor or 0 if
/// compressor does not hint it's optimal block size
///
/// A pointer to the compressor instance
/// A integer value of the compressor block size
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetBlockSize(IntPtr compressor)
{
long result = _methodTable.GetBlockSize(compressor);
ThrowHelper.ThrowIfError(result);
return (uint)result;
}
///
/// Gets the compressor type of the specified compressor
///
/// A pointer to the compressor instance
/// A enum value that represents the compressor type
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CompressionMethod GetCompressorType(IntPtr compressor)
{
CompressionMethod result = _methodTable.GetCompType(compressor);
ThrowHelper.ThrowIfError((long)result);
return result;
}
///
/// Gets the compression level of the specified compressor
///
/// A pointer to the compressor instance
/// The of the current compressor
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CompressionLevel GetCompressorLevel(IntPtr compressor)
{
CompressionLevel result = _methodTable.GetCompLevel(compressor);
ThrowHelper.ThrowIfError((long)result);
return result;
}
///
/// Allocates a new compressor instance of the specified type and compression level
///
/// The compressor type to allocate
/// The desired compression level
/// A pointer to the newly allocated compressor instance
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IntPtr AllocateCompressor(CompressionMethod type, CompressionLevel level)
{
IntPtr result = _methodTable.Alloc(type, level);
ThrowHelper.ThrowIfError(result.ToInt64());
return result;
}
///
/// Frees the specified compressor instance
///
/// A pointer to the valid compressor instance to free
/// A value indicating the result of the free operation
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FreeCompressor(IntPtr compressor)
{
int result = _methodTable.Free(compressor);
ThrowHelper.ThrowIfError(result);
if(result == 0)
{
throw new NativeCompressionException("Failed to free the compressor instance");
}
}
///
/// Frees the specified compressor instance without raising exceptions
///
/// A pointer to the valid compressor instance to free
/// A value indicating the result of the free operation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int FreeSafeCompressor(IntPtr compressor) => _methodTable.Free(compressor);
///
/// Determines the output size of a given input size and flush mode for the specified compressor
///
/// A pointer to the compressor instance
/// The size of the input block to compress
/// A value that specifies a flush operation
/// Returns the size of the required output buffer
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetOutputSize(IntPtr compressor, ulong inputSize, int flush)
{
long result = _methodTable.GetOutputSize(compressor, inputSize, flush);
ThrowHelper.ThrowIfError(result);
return (ulong)result;
}
///
/// Compresses a block of data using the specified compressor instance
///
/// The compressor instance used to compress data
/// A pointer to the compression operation structure
/// The result of the operation
///
///
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe int CompressBlock(IntPtr compressor, CompressionOperation* operation)
{
int result = _methodTable.Compress(compressor, operation);
ThrowHelper.ThrowIfError(result);
return result;
}
///
~LibraryWrapper()
{
_methodTable = default;
_lib.Dispose();
}
///
/// Manually releases the library
///
public void Dispose()
{
_methodTable = default;
_lib.Dispose();
GC.SuppressFinalize(this);
}
private readonly struct MethodTable
{
public GetSupportedMethodsDelegate GetMethods { get; init; }
public GetBlockSizeDelegate GetBlockSize { get; init; }
public GetCompressorTypeDelegate GetCompType { get; init; }
public GetCompressorLevelDelegate GetCompLevel { get; init; }
public AllocateCompressorDelegate Alloc { get; init; }
public FreeCompressorDelegate Free { get; init; }
public GetCompressedSizeDelegate GetOutputSize { get; init; }
public CompressBlockDelegate Compress { get; init; }
}
}
}