From d097eba3c2be78f5c01283bd9e172d41ff8f0b1a Mon Sep 17 00:00:00 2001 From: Hajin Jang Date: Tue, 8 Aug 2023 02:11:42 +0900 Subject: [PATCH] Prepare v2.2.0 release --- CHANGELOG.md | 31 +++++-- Joveler.DynLoader.Tests/SimpleZLib.cs | 5 +- Joveler.DynLoader.Tests/SimpleZLibTests.cs | 3 + Joveler.DynLoader/DynLoaderBase.cs | 50 ++++++----- Joveler.DynLoader/Joveler.DynLoader.csproj | 6 +- Joveler.DynLoader/LoadManagerBase.cs | 18 ++-- Joveler.DynLoader/NativeMethods.cs | 2 +- Joveler.DynLoader/PlatformConvention.cs | 2 +- Joveler.DynLoader/SafeLibHandle.cs | 2 +- README.md | 6 ++ USAGE.md | 99 +++++++++++++++++++++- 11 files changed, 178 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ef9e3..e73f49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,26 @@ ## v2.x +### v2.2.0 + +Released on 2023-08-08. + +- Allow passing a custom object when loading a native library. + - Add `LoadManagerBase.GlobalInit()` overloadings with custom object parameter. + - Add `DynLoaderBase.LoadLibrary()` overloadings with custom object parameter. + - Add virtual method `DynLoaderBase.HandleLoadData()`. + - Add helper method `DynLoaderBase.HasFuncSymbol()`. + +### v2.1.1 + +Released on 2022-02-15. + +- Official support for ARM64 macOS. +- Unify .NET Framework 4.5.1 codebase and .NET Standard 2.0 codebase. + ### v2.1.0 -Release in 2021.04.05. +Released on 2021-04-05. - Avoid calling virtual methods from constructors in `DynLoaderBase`. - Users must call `DynLoaderBase.LoadLibrary` after creating an instance. @@ -14,7 +31,7 @@ Release in 2021.04.05. ### v2.0.0 -Released in 2020.04.24. +Released on 2020-04-24. - Use `NativeLoader` on .NET Core 3.x build. - `DynLoaderBase` now throws [DllNotFoundException](https://docs.microsoft.com/en-US/dotnet/api/system.dllnotfoundexception) and [EntryPointNotFoundException](https://docs.microsoft.com/en-US/dotnet/api/system.entrypointnotfoundexception) instead of [ArgumentException](https://docs.microsoft.com/en-US/dotnet/api/system.argumentexception) and [InvalidOperationException](https://docs.microsoft.com/en-us/dotnet/api/system.invalidoperationexception). @@ -30,31 +47,31 @@ Released in 2020.04.24. ### v1.3.0 -Released in 2020.02.29. +Released on 2020-02-29. - Add `size_t` helper methods. - Rename AutoStringToCoTaskMem() into StringToCoTaskMemAuto(). ### v1.2.1 -Released in 2019.10.31. +Released on 2019-10-31. - Address `libdl.so` naming issue for CentOS ([#1](https://github.com/ied206/Joveler.DynLoader/issues/1)) ### v1.2.0 -Released in 2019.10.16. +Released on 2019-10-16. - Add platform convention helper properties and methods ### v1.1.0 -Released in 2019.10.15. +Released on 2019-10-15. - Add `LoadManagerBase` abstract class ### v1.0.0 -Released in 2019.10.15. +Released on 2019-10-15. - The initial release of the cross-platform native dynamic library loader for .NET. diff --git a/Joveler.DynLoader.Tests/SimpleZLib.cs b/Joveler.DynLoader.Tests/SimpleZLib.cs index 08720ee..86bc43c 100644 --- a/Joveler.DynLoader.Tests/SimpleZLib.cs +++ b/Joveler.DynLoader.Tests/SimpleZLib.cs @@ -40,6 +40,7 @@ protected override string DefaultLibFileName public bool HasUnknownSymbol { get; private set; } = true; public bool HasCrc32Symbol { get; private set; } = false; + public IntPtr UnknownRawPtr { get; private set; } = IntPtr.Zero; public IntPtr DeflateRawPtr { get; private set; } = IntPtr.Zero; #endregion @@ -48,8 +49,8 @@ protected override string DefaultLibFileName internal Cdecl _cdecl = new Cdecl(); #endregion - #region ParseCustomData - protected override void ParseLoadData(object data) + #region HandleCustomData + protected override void HandleLoadData(object data) { if (!(data is SimpleZLibLoadData loadData)) return; diff --git a/Joveler.DynLoader.Tests/SimpleZLibTests.cs b/Joveler.DynLoader.Tests/SimpleZLibTests.cs index c6473e4..be958fd 100644 --- a/Joveler.DynLoader.Tests/SimpleZLibTests.cs +++ b/Joveler.DynLoader.Tests/SimpleZLibTests.cs @@ -161,9 +161,12 @@ private void ManagerTemplate(string libPath, bool isWindowsStdcall) Assert.IsTrue(dupInitGuard); Console.WriteLine(manager.Lib.ZLibVersion()); + Console.WriteLine($"UnknownRawPtr = 0x{manager.Lib.UnknownRawPtr:X8}"); + Console.WriteLine($"DeflateRawPtr = 0x{manager.Lib.DeflateRawPtr:X8}"); Assert.IsFalse(manager.Lib.HasUnknownSymbol); Assert.IsTrue(manager.Lib.HasCrc32Symbol); + Assert.AreEqual(IntPtr.Zero, manager.Lib.UnknownRawPtr); Assert.AreNotEqual(IntPtr.Zero, manager.Lib.DeflateRawPtr); bool dupCleanGuard = false; diff --git a/Joveler.DynLoader/DynLoaderBase.cs b/Joveler.DynLoader/DynLoaderBase.cs index 726b914..b981c47 100644 --- a/Joveler.DynLoader/DynLoaderBase.cs +++ b/Joveler.DynLoader/DynLoaderBase.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License @@ -146,20 +146,20 @@ public void LoadLibrary(string libPath) } /// - /// Load a native dynamic library from a path of `DefaultLibFileName`. + /// Load a native dynamic library from a path of `DefaultLibFileName`, with custom object. /// - /// Custom object to be passed to . - public void LoadLibrary(object data) + /// Custom object has been passed to . + public void LoadLibrary(object loadData) { - LoadLibrary(null, data); + LoadLibrary(null, loadData); } /// - /// Load a native dynamic library from a given path. + /// Load a native dynamic library from a given path, with custom object. /// /// A native library file to load. - /// Custom object to be passed to . - public void LoadLibrary(string libPath, object data) + /// Custom object has been passed to . + public void LoadLibrary(string libPath, object loadData) { // Should DynLoaderBase use default library filename? if (libPath == null) @@ -172,7 +172,7 @@ public void LoadLibrary(string libPath, object data) LibPath = libPath; // Parse custom load data - ParseLoadData(data); + HandleLoadData(loadData); // Use .NET Core's NativeLibrary when available #if NETCOREAPP @@ -269,11 +269,11 @@ public void LoadLibrary(string libPath, object data) #region (protected) GetFuncPtr, GetRawFuncPtr, HasFuncSymbol /// - /// Get a delegate of a native function from a library. - /// The method will use name of the given delegate T as function symbol. + /// Get a delegate of a native function from the library. + /// The method will use name of the given delegate T as a function symbol. /// - /// Delegate type of a native function. - /// Delegate instance of a native function. + /// Delegate type of the native function. + /// Delegate instance of the native function. /// Throwen if the given function symbol was not found. protected T GetFuncPtr() where T : Delegate { @@ -282,11 +282,11 @@ protected T GetFuncPtr() where T : Delegate } /// - /// Get a delegate of a native function from a library. + /// Get a delegate of a native function from the library. /// - /// Delegate type of a native function. + /// Delegate type of the native function. /// Name of the exported function symbol. - /// Delegate instance of a native function. + /// Delegate instance of the native function. /// Throwen if the given function symbol was not found. protected T GetFuncPtr(string funcSymbol) where T : Delegate { @@ -342,6 +342,11 @@ protected T GetFuncPtr(string funcSymbol) where T : Delegate return Marshal.GetDelegateForFunctionPointer(funcPtr); } + /// + /// Get a raw pointer of a native function from the library. + /// + /// Name of the exported function symbol. + /// Raw pointer address of the native function. Returns IntPtr.Zero if the symbol was not found. protected IntPtr GetRawFuncPtr(string funcSymbol) { IntPtr funcPtr = IntPtr.Zero; @@ -371,6 +376,11 @@ protected IntPtr GetRawFuncPtr(string funcSymbol) return funcPtr; } + /// + /// Check if the library has a native function symbol. + /// + /// Name of the exported function symbol. + /// Returns true if the address of the exported symbol was found. protected bool HasFuncSymbol(string funcSymbol) { return GetRawFuncPtr(funcSymbol) != IntPtr.Zero; @@ -388,12 +398,12 @@ protected bool HasFuncSymbol(string funcSymbol) protected abstract string DefaultLibFileName { get; } #endregion - #region (virtual) ParseLoadData + #region (virtual) HandleLoadData /// - /// Parse custom object passed into . + /// Handle custom object passed into . /// - /// Custom object to be passed to . - protected virtual void ParseLoadData(object data) + /// Custom object has been passed to . + protected virtual void HandleLoadData(object data) { } diff --git a/Joveler.DynLoader/Joveler.DynLoader.csproj b/Joveler.DynLoader/Joveler.DynLoader.csproj index e962e12..37bc163 100644 --- a/Joveler.DynLoader/Joveler.DynLoader.csproj +++ b/Joveler.DynLoader/Joveler.DynLoader.csproj @@ -15,7 +15,11 @@ Supports Windows, Linux, and macOS. https://github.com/ied206/Joveler.DynLoader images\Logo.png https://github.com/ied206/Joveler.DynLoader - - Allow passing arbitrary objects to GlobalInit(). + - Allow passing a custom object when loading a native library. + - Add `LoadManagerBase.GlobalInit()` overloadings with custom object parameter. + - Add `DynLoaderBase.LoadLibrary()` overloadings with custom object parameter. + - Add virtual method `DynLoaderBase.HandleLoadData()`. + - Add helper method `DynLoaderBase.HasFuncSymbol()`. native pinvoke interop dynamic library loader dll so dylib diff --git a/Joveler.DynLoader/LoadManagerBase.cs b/Joveler.DynLoader/LoadManagerBase.cs index 3c02c50..b2440de 100644 --- a/Joveler.DynLoader/LoadManagerBase.cs +++ b/Joveler.DynLoader/LoadManagerBase.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License @@ -161,20 +161,20 @@ public void GlobalInit(string libPath) } /// - /// Create DynLoaderBase singleton instance in a thread-safe way. + /// Create DynLoaderBase singleton instance in a thread-safe way, with a custom object. /// - /// Custom object to be passed to . - public void GlobalInit(object data) + /// Custom object to be passed to . + public void GlobalInit(object loadData) { - GlobalInit(null, data); + GlobalInit(null, loadData); } /// - /// Create DynLoaderBase singleton instance in a thread-safe way. + /// Create DynLoaderBase singleton instance in a thread-safe way, with a custom object. /// /// A native library file to load. - /// Custom object to be passed to . - public void GlobalInit(string libPath, object data) + /// Custom object to be passed to . + public void GlobalInit(string libPath, object loadData) { lock (_loadLock) { @@ -183,7 +183,7 @@ public void GlobalInit(string libPath, object data) Lib = CreateLoader(); PreInitHook(); - Lib.LoadLibrary(libPath, data); + Lib.LoadLibrary(libPath, loadData); PostInitHook(); } } diff --git a/Joveler.DynLoader/NativeMethods.cs b/Joveler.DynLoader/NativeMethods.cs index f03ac53..9cfb867 100644 --- a/Joveler.DynLoader/NativeMethods.cs +++ b/Joveler.DynLoader/NativeMethods.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License diff --git a/Joveler.DynLoader/PlatformConvention.cs b/Joveler.DynLoader/PlatformConvention.cs index e43cef9..0a3ee10 100644 --- a/Joveler.DynLoader/PlatformConvention.cs +++ b/Joveler.DynLoader/PlatformConvention.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License diff --git a/Joveler.DynLoader/SafeLibHandle.cs b/Joveler.DynLoader/SafeLibHandle.cs index 96f6dd4..e6f822f 100644 --- a/Joveler.DynLoader/SafeLibHandle.cs +++ b/Joveler.DynLoader/SafeLibHandle.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License diff --git a/README.md b/README.md index f11da23..9bce709 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ The library provides advanced p/invoke functionality of C functions using [Nativ | Linux | NativeLibrary, libdl | x64, armhf, arm64 | | macOS | NativeLibrary, libdl | x64, arm64 | +### Helps needed! + +- Android support and testing + - While I guess Android support on .NET Standard 2.0 target may be similar to Linux, I do not have any time to test on Android right now. + - On .NET/.NET Core, availibity of the `NativeLibrary` on Android has not been tested yet. + ## Usage See [USAGE.md](./USAGE.md). diff --git a/USAGE.md b/USAGE.md index 6bd2c79..9976e8d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -48,7 +48,8 @@ Follow these steps to use a wrapper library. public static void GlobalCleanup() => Manager.GlobalCleanup(); } ``` -1. Call `GlobalInit()` to load native functions. +1. Call one of `GlobalInit` functions to load native functions. + - You may call `GlobalInit(object loadData)` or `GlobalInit(string libPath, object loadData)` instead to pass a custom object. It would be handled by `DynLoaderBase.HandleLoadData()` later. 1. Call delegate instances to call corresponding native functions. ## DynLoaderBase @@ -105,9 +106,37 @@ public SimpleFileMagic() : base() { } ### LoadLibrary +```csharp +/// +/// Load a native dynamic library from a path of `DefaultLibFileName`. +/// +public void LoadLibrary(); +/// +/// Load a native dynamic library from a given path. +/// +/// A native library file to load. +public void LoadLibrary(string libPath); +/// +/// Load a native dynamic library from a path of `DefaultLibFileName`, with custom object. +/// +/// Custom object has been passed to . +public void LoadLibrary(object loadData); +/// +/// Load a native dynamic library from a given path, with custom object. +/// +/// A native library file to load. +/// Custom object has been passed to . +public void LoadLibrary(string libPath, object loadData); +``` + After creating an instance of a derived class, make sure to call `LoadLibrary()` to load a native library. After that, you can invoke extern native functions via delegate instances. -`LoadLibrary(string libPath)` loads that specific native library. The parameterless version loads the default native library from the base system. +| Signature | Description | +|-----------------|-------------| +| `LoadLibrary()` | Loads the default native library from the base system. Works only if `DefaultLibFileName` is not null. | +| `LoadLibrary(string libPath)` | Loads specific native library from the path. | +| `LoadLibrary(object loadData)` | Pass a custom object, which would be handled by `HandleLoadData()`. Otherwise it is equal to `LoadLibrary()`. | +| `LoadLibrary(string libPath, object loadData)` | Pass a custom object, which would be handled by `HandleLoadData()`. Otherwise it is equal to `LoadLibrary(string libPath)`. | When it fails to find a native library, [DllNotFoundException](https://docs.microsoft.com/en-US/dotnet/api/system.dllnotfoundexception?view=netcore-3.1) is thrown. @@ -138,6 +167,11 @@ protected abstract void LoadFunctions(); /// Clear pointer of native functions. Called in Dispose(bool). /// protected abstract void ResetFunctions(); +/// +/// Handle custom object passed into . +/// +/// Custom object has been passed to . +protected virtual void HandleLoadData(object data) { } ``` #### LoadFunctions() @@ -167,9 +201,9 @@ protected override void LoadFunctions() #### ResetFunctions() -Override `ResetFunctions()` when you want to explicitly clear native resources and delegate assignments. +Always override `ResetFunctions()` with a code clearing native resources and delegate assignments. -Usually, the override of this method is not required, as the library handle is automatically cleared when the instance is disposed of. But if you need to clear delegate assignments manually, you have to implement them. +In most platforms, simply assigning `null` to function pointers is suffice. #### DefaultLibFileName @@ -193,6 +227,63 @@ protected override string DefaultLibFileName } ``` +#### HandleLoadData() + +Override `HandleLoadData()` to put a business logic to handle custom object which has been passed to `LoadManagerBase.GlobalInit()`. + +This functions is called after the `LibPath` property is set to native library path, and before `LoadFunctions()` is called. + +You are able to access the results of `HandleLoadData()` from `LoadFunctions()`. + +For example, you may need to override it to support both `cdecl` and `stdcall` ABIs of the same library. + +**Example** + +```csharp +public class SimpleZLibLoadData +{ + public bool IsWindowsStdcall { get; set; } = true; +} +``` + +```csharp +private bool _isWindowsStdcall = true; + +protected override void HandleLoadData(object data) +{ + if (!(data is SimpleZLibLoadData loadData)) + return; + + _isWindowsStdcall = loadData.IsWindowsStdcall; +} + +internal class Stdcall +{ + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public unsafe delegate uint adler32(uint adler, byte* buf, uint len); + public adler32 Adler32; +} + +internal class Cdecl +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate uint adler32(uint adler, byte* buf, uint len); + public adler32 Adler32; +} + +protected override void LoadFunctions() +{ + if (_isWindowsStdcall) + { + _stdcall.Adler32 = GetFuncPtr(nameof(Stdcall.adler32)); + } + else + { + _cdecl.Adler32 = GetFuncPtr(nameof(Cdecl.adler32)); + } +} +``` + ### Platform Conventions Native function signatures are changed by platform differences, such as OS and architecture. Sometimes you have to maintain two or more signature sets to accommodate this difference. To make your life easy, `DynLoaderBase` provides helper properties and methods.