/* * 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; } } } }