diff --git a/.gitignore b/.gitignore index d63c2a7..b17da51 100644 --- a/.gitignore +++ b/.gitignore @@ -38,10 +38,10 @@ bld/ [Ll]og/ # Do not ignore Precompiled directory -!/**/Precompiled/x86/ -!/**/Precompiled/x64/ -!/**/Precompiled/armhf/ -!/**/Precompiled/arm64/ +!/**/runtimes/**/native/ + +# Thumbs.db +Thumbs.db # macOS DS_Store guard .DS_Store @@ -173,6 +173,7 @@ AutoTest.Net/ # Installshield output folder [Ee]xpress/ + # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT 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/GhostErrorTests.cs b/Joveler.DynLoader.Tests/GhostErrorTests.cs index d4117da..644ac97 100644 --- a/Joveler.DynLoader.Tests/GhostErrorTests.cs +++ b/Joveler.DynLoader.Tests/GhostErrorTests.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License @@ -59,7 +59,7 @@ public void LoadFunctionError() try { GhostFunction lib = new GhostFunction(); - lib.LoadLibrary(TestSetup.PackagedZLibPath); + lib.LoadLibrary(TestSetup.PackagedZLibPathStdcall); } catch (EntryPointNotFoundException e) { diff --git a/Joveler.DynLoader.Tests/Joveler.DynLoader.Tests.csproj b/Joveler.DynLoader.Tests/Joveler.DynLoader.Tests.csproj index 38fb8b5..a6589b2 100644 --- a/Joveler.DynLoader.Tests/Joveler.DynLoader.Tests.csproj +++ b/Joveler.DynLoader.Tests/Joveler.DynLoader.Tests.csproj @@ -1,10 +1,8 @@  - - net48;net6.0 - - net6.0 + + net481;net6.0 net6.0 @@ -19,13 +17,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + @@ -48,8 +46,8 @@ - - + + x86\%(FileName)%(Extension) PreserveNewest @@ -58,12 +56,16 @@ x64\%(FileName)%(Extension) PreserveNewest + + arm64\%(FileName)%(Extension) + PreserveNewest + - - + + runtimes\win-x86\native\%(FileName)%(Extension) PreserveNewest @@ -92,7 +94,10 @@ runtimes\osx-x64\native\%(FileName)%(Extension) PreserveNewest + + runtimes\osx-arm64\native\%(FileName)%(Extension) + PreserveNewest + - diff --git a/Joveler.DynLoader.Tests/PlatformConventionTests.cs b/Joveler.DynLoader.Tests/PlatformConventionTests.cs index 663d058..68373c3 100644 --- a/Joveler.DynLoader.Tests/PlatformConventionTests.cs +++ b/Joveler.DynLoader.Tests/PlatformConventionTests.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2020 Hajin Jang + Copyright (C) 2020-2023 Hajin Jang Licensed under MIT License. MIT License diff --git a/Joveler.DynLoader.Tests/SampleScript.netfx.targets b/Joveler.DynLoader.Tests/SampleScript.netfx.targets index ef21b43..1928ca3 100644 --- a/Joveler.DynLoader.Tests/SampleScript.netfx.targets +++ b/Joveler.DynLoader.Tests/SampleScript.netfx.targets @@ -33,6 +33,7 @@ HasTrailingSlash('$(MSBuildThisFileDirectory)')"> + + arm64\%(FileName)%(Extension) + PreserveNewest + @@ -89,6 +94,16 @@ + + + + + + + + + bin\arm64\%(Filename)%(Extension) + + + @@ -166,18 +196,21 @@ $(PostBuildEventDependsOn); CopyInteropLibFiles_x86; CopyInteropLibFiles_x64; + CopyInteropLibFiles_arm4; CopyInteropMgcFiles; $(BuildDependsOn); CopyInteropLibFiles_x86; CopyInteropLibFiles_x64; + CopyInteropLibFiles_arm64; CopyInteropMgcFiles; $(CleanDependsOn); CleanInteropLibFiles_x86; CleanInteropLibFiles_x64; + CleanInteropLibFiles_arm64; CleanInteropMgcFiles; @@ -188,16 +221,11 @@ ****************************************************************************** --> - + CollectInteropLibFiles_x86; CollectInteropLibFiles_x64; + CollectInteropLibFiles_arm64; CollectInteropMgcFiles; $(PipelineCollectFilesPhaseDependsOn); diff --git a/Joveler.DynLoader.Tests/SimpleFIleMagicManager.cs b/Joveler.DynLoader.Tests/SimpleFIleMagicManager.cs index 3ec61ce..624b66d 100644 --- a/Joveler.DynLoader.Tests/SimpleFIleMagicManager.cs +++ b/Joveler.DynLoader.Tests/SimpleFIleMagicManager.cs @@ -1,4 +1,9 @@ -using System; +/* + Written by Hajin Jang. + Released under public domain. +*/ + +using System; namespace Joveler.DynLoader.Tests { diff --git a/Joveler.DynLoader.Tests/SimpleFileMagicTests.cs b/Joveler.DynLoader.Tests/SimpleFileMagicTests.cs index c13b78c..e1adc5f 100644 --- a/Joveler.DynLoader.Tests/SimpleFileMagicTests.cs +++ b/Joveler.DynLoader.Tests/SimpleFileMagicTests.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.Tests/SimplePlatformTests.cs b/Joveler.DynLoader.Tests/SimplePlatformTests.cs index 67fc668..1f950ce 100644 --- a/Joveler.DynLoader.Tests/SimplePlatformTests.cs +++ b/Joveler.DynLoader.Tests/SimplePlatformTests.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.Tests/SimpleZLib.cs b/Joveler.DynLoader.Tests/SimpleZLib.cs index 2c8cd35..86bc43c 100644 --- a/Joveler.DynLoader.Tests/SimpleZLib.cs +++ b/Joveler.DynLoader.Tests/SimpleZLib.cs @@ -8,6 +8,11 @@ Released under public domain. namespace Joveler.DynLoader.Tests { + public class SimpleZLibLoadData + { + public bool IsWindowsStdcall { get; set; } = true; + } + /// /// Sample representation of zlib, includes only adler32 and crc32 checksum /// @@ -30,39 +35,132 @@ protected override string DefaultLibFileName throw new PlatformNotSupportedException(); } } + + private bool _isWindowsStdcall = true; + + 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 + + #region Stdcall and Cdecl + internal Stdcall _stdcall = new Stdcall(); + internal Cdecl _cdecl = new Cdecl(); + #endregion + + #region HandleCustomData + protected override void HandleLoadData(object data) + { + if (!(data is SimpleZLibLoadData loadData)) + return; + + _isWindowsStdcall = loadData.IsWindowsStdcall; + Console.WriteLine($"libPath = {LibPath}"); + Console.WriteLine($"isWindowsStdcall = {_isWindowsStdcall}"); + } #endregion #region LoadFunctions, ResetFunctions /// protected override void LoadFunctions() { - Adler32 = GetFuncPtr(nameof(adler32)); - Crc32 = GetFuncPtr(nameof(crc32)); - ZLibVersionPtr = GetFuncPtr(); + HasUnknownSymbol = HasFuncSymbol("UnknownSymbol"); + HasCrc32Symbol = HasFuncSymbol(nameof(Cdecl.crc32)); + DeflateRawPtr = GetRawFuncPtr("deflate"); + + if (_isWindowsStdcall) + { + _stdcall.Adler32 = GetFuncPtr(nameof(Stdcall.adler32)); + _stdcall.Crc32 = GetFuncPtr(nameof(Stdcall.crc32)); + _stdcall.ZLibVersionPtr = GetFuncPtr(); + } + else + { + _cdecl.Adler32 = GetFuncPtr(nameof(Cdecl.adler32)); + _cdecl.Crc32 = GetFuncPtr(nameof(Cdecl.crc32)); + _cdecl.ZLibVersionPtr = GetFuncPtr(); + } } /// protected override void ResetFunctions() { - Adler32 = null; - Crc32 = null; - ZLibVersionPtr = null; + if (_isWindowsStdcall) + { + _stdcall.Adler32 = null; + _stdcall.Crc32 = null; + _stdcall.ZLibVersionPtr = null; + } + else + { + _cdecl.Adler32 = null; + _cdecl.Crc32 = null; + _cdecl.ZLibVersionPtr = null; + } } #endregion #region zlib Function Pointers - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public unsafe delegate uint adler32(uint adler, byte* buf, uint len); - public adler32 Adler32; - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public unsafe delegate uint crc32(uint crc, byte* buf, uint len); - public crc32 Crc32; - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate IntPtr zlibVersion(); - private zlibVersion ZLibVersionPtr; - public string ZLibVersion() => Marshal.PtrToStringAnsi(ZLibVersionPtr()); + public unsafe uint Adler32(uint adler, byte* buf, uint len) + { + if (_isWindowsStdcall) + return _stdcall.Adler32(adler, buf, len); + else + return _cdecl.Adler32(adler, buf, len); + } + + public unsafe uint Crc32(uint crc, byte* buf, uint len) + { + if (_isWindowsStdcall) + return _stdcall.Crc32(crc, buf, len); + else + return _cdecl.Crc32(crc, buf, len); + } + + public string ZLibVersion() + { + IntPtr strPtr = IntPtr.Zero; + if (_isWindowsStdcall) + strPtr = _stdcall.ZLibVersionPtr(); + else + strPtr = _cdecl.ZLibVersionPtr(); + return Marshal.PtrToStringAnsi(strPtr); + } + + internal class Stdcall + { + private const CallingConvention CallConv = CallingConvention.Winapi; + + [UnmanagedFunctionPointer(CallConv)] + public unsafe delegate uint adler32(uint adler, byte* buf, uint len); + public adler32 Adler32; + + [UnmanagedFunctionPointer(CallConv)] + public unsafe delegate uint crc32(uint crc, byte* buf, uint len); + public crc32 Crc32; + + [UnmanagedFunctionPointer(CallConv)] + public delegate IntPtr zlibVersion(); + internal zlibVersion ZLibVersionPtr; + + } + internal class Cdecl + { + private const CallingConvention CallConv = CallingConvention.Cdecl; + + [UnmanagedFunctionPointer(CallConv)] + public unsafe delegate uint adler32(uint adler, byte* buf, uint len); + public adler32 Adler32; + + [UnmanagedFunctionPointer(CallConv)] + public unsafe delegate uint crc32(uint crc, byte* buf, uint len); + public crc32 Crc32; + + [UnmanagedFunctionPointer(CallConv)] + public delegate IntPtr zlibVersion(); + internal zlibVersion ZLibVersionPtr; + } #endregion } } diff --git a/Joveler.DynLoader.Tests/SimpleZLibManager.cs b/Joveler.DynLoader.Tests/SimpleZLibManager.cs index 2d2b839..7588719 100644 --- a/Joveler.DynLoader.Tests/SimpleZLibManager.cs +++ b/Joveler.DynLoader.Tests/SimpleZLibManager.cs @@ -1,4 +1,9 @@ -namespace Joveler.DynLoader.Tests +/* + Written by Hajin Jang. + Released under public domain. +*/ + +namespace Joveler.DynLoader.Tests { public class SimpleZLibManager : LoadManagerBase { diff --git a/Joveler.DynLoader.Tests/SimpleZLibTests.cs b/Joveler.DynLoader.Tests/SimpleZLibTests.cs index 3c7c156..be958fd 100644 --- a/Joveler.DynLoader.Tests/SimpleZLibTests.cs +++ b/Joveler.DynLoader.Tests/SimpleZLibTests.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License @@ -38,7 +38,7 @@ public class SimpleZLibTests [ClassInitialize] public static void Init(TestContext _) { - _zlibs = new SimpleZLib[] { TestSetup.ExplicitZLib, TestSetup.ImplicitZLib }; + _zlibs = new SimpleZLib[] { TestSetup.ExplicitStdcallZLib, TestSetup.ExplicitCdeclZLib, TestSetup.ImplicitZLib }; _zlibs = _zlibs.Where(z => z != null).ToArray(); } @@ -105,9 +105,9 @@ public void Version() } [TestMethod] - public void CreateDispose() + public void StdcallCreateDispose() { - string libPath = TestSetup.PackagedZLibPath; + string libPath = TestSetup.PackagedZLibPathStdcall; using (SimpleZLib zlib = new SimpleZLib()) { zlib.LoadLibrary(libPath); @@ -116,17 +116,43 @@ public void CreateDispose() } [TestMethod] - public void Manager() + public void CdeclCreateDispose() { - string libPath = TestSetup.PackagedZLibPath; + string libPath = TestSetup.PackagedZLibPathCdecl; + using (SimpleZLib zlib = new SimpleZLib()) + { + zlib.LoadLibrary(libPath); + zlib.ZLibVersion(); + } + } + [TestMethod] + public void StdcallManager() + { + string libPath = TestSetup.PackagedZLibPathStdcall; + ManagerTemplate(libPath, true); + } + + [TestMethod] + public void CdeclManager() + { + string libPath = TestSetup.PackagedZLibPathCdecl; + ManagerTemplate(libPath, false); + } + + private void ManagerTemplate(string libPath, bool isWindowsStdcall) + { SimpleZLibManager manager = new SimpleZLibManager(); + SimpleZLibLoadData loadData = new SimpleZLibLoadData() + { + IsWindowsStdcall = isWindowsStdcall, + }; bool dupInitGuard = false; - manager.GlobalInit(libPath); + manager.GlobalInit(libPath, loadData); try { - manager.GlobalInit(); + manager.GlobalInit(loadData); } catch (InvalidOperationException) { @@ -134,7 +160,14 @@ public void Manager() } Assert.IsTrue(dupInitGuard); - manager.Lib.ZLibVersion(); + 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; manager.GlobalCleanup(); diff --git a/Joveler.DynLoader.Tests/TestSetup.cs b/Joveler.DynLoader.Tests/TestSetup.cs index 4b71d66..ea768f9 100644 --- a/Joveler.DynLoader.Tests/TestSetup.cs +++ b/Joveler.DynLoader.Tests/TestSetup.cs @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang Licensed under MIT License. MIT License @@ -27,6 +27,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using System; using System.IO; using System.Runtime.InteropServices; +using System.Text; namespace Joveler.DynLoader.Tests { @@ -34,8 +35,10 @@ namespace Joveler.DynLoader.Tests public class TestSetup { public static string SampleDir { get; private set; } - public static string PackagedZLibPath { get; private set; } - public static SimpleZLib ExplicitZLib { get; private set; } + public static string PackagedZLibPathStdcall { get; private set; } + public static string PackagedZLibPathCdecl { get; private set; } + public static SimpleZLib ExplicitStdcallZLib { get; private set; } + public static SimpleZLib ExplicitCdeclZLib { get; private set; } public static SimpleZLib ImplicitZLib { get; private set; } public static string PackagedMagicPath { get; private set; } public static SimpleFileMagic ExplicitMagic { get; private set; } @@ -52,14 +55,13 @@ public static void AssemblyInitalize(TestContext ctx) string libBaseDir = Path.GetFullPath(Path.Combine(TestHelper.GetProgramAbsolutePath(), "..", "..", "..")); #endif - const string zlibDllName = "zlibwapi.dll"; + const string zlibStdcallDllName = "zlibwapi.dll"; + const string zlibCdeclDllName = "zlib1.dll"; const string magicDllName = "libmagic-1.dll"; -#if NETCOREAPP const string zlibSoName = "libz.so"; const string zlibDylibName = "libz.dylib"; const string magicSoName = "libmagic.so"; const string magicDylibName = "libmagic.dylib"; -#endif string arch = null; switch (RuntimeInformation.ProcessArchitecture) @@ -71,7 +73,7 @@ public static void AssemblyInitalize(TestContext ctx) arch = "x64"; break; case Architecture.Arm: - arch = "armhf"; + arch = "arm"; break; case Architecture.Arm64: arch = "arm64"; @@ -80,12 +82,12 @@ public static void AssemblyInitalize(TestContext ctx) bool implicitLoadZLib = false; bool implicitLoadMagic = false; - bool implicitLoadPlataform = false; + bool implicitLoadPlatform = false; string libDir; #if NETFRAMEWORK libDir = Path.Combine(libBaseDir, arch); -#elif NETCOREAPP +#else libDir = Path.Combine(libBaseDir, "runtimes"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) libDir = Path.Combine(libDir, "win-"); @@ -95,33 +97,40 @@ public static void AssemblyInitalize(TestContext ctx) libDir = Path.Combine(libDir, "osx-"); libDir += arch; libDir = Path.Combine(libDir, "native"); +#endif if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif { - PackagedZLibPath = Path.Combine(libDir, zlibDllName); + PackagedZLibPathStdcall = Path.Combine(libDir, zlibStdcallDllName); + PackagedZLibPathCdecl = Path.Combine(libDir, zlibCdeclDllName); PackagedMagicPath = Path.Combine(libDir, magicDllName); - implicitLoadPlataform = true; + implicitLoadPlatform = true; } -#if NETCOREAPP else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - PackagedZLibPath = Path.Combine(libDir, zlibSoName); + PackagedZLibPathStdcall = Path.Combine(libDir, zlibSoName); PackagedMagicPath = Path.Combine(libDir, magicSoName); implicitLoadZLib = true; implicitLoadMagic = true; - implicitLoadPlataform = true; + implicitLoadPlatform = true; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - PackagedZLibPath = Path.Combine(libDir, zlibDylibName); + PackagedZLibPathStdcall = Path.Combine(libDir, zlibDylibName); PackagedMagicPath = Path.Combine(libDir, magicDylibName); implicitLoadZLib = true; } -#endif - ExplicitZLib = new SimpleZLib(); - ExplicitZLib.LoadLibrary(PackagedZLibPath); + ExplicitStdcallZLib = new SimpleZLib(); + ExplicitStdcallZLib.LoadLibrary(PackagedZLibPathStdcall, new SimpleZLibLoadData() + { + IsWindowsStdcall = true, + }); + ExplicitCdeclZLib = new SimpleZLib(); + ExplicitCdeclZLib.LoadLibrary(PackagedZLibPathCdecl, new SimpleZLibLoadData() + { + IsWindowsStdcall = false, + }); if (implicitLoadZLib) { ImplicitZLib = new SimpleZLib(); @@ -136,7 +145,7 @@ public static void AssemblyInitalize(TestContext ctx) ImplicitMagic.LoadLibrary(); } - if (implicitLoadPlataform) + if (implicitLoadPlatform) { PlatformLib = new SimplePlatform(); PlatformLib.LoadLibrary(); @@ -146,15 +155,15 @@ public static void AssemblyInitalize(TestContext ctx) [AssemblyCleanup] public static void AssemblyCleanup() { - ExplicitZLib?.Dispose(); + ExplicitStdcallZLib?.Dispose(); ImplicitZLib?.Dispose(); ExplicitMagic?.Dispose(); ImplicitMagic?.Dispose(); PlatformLib?.Dispose(); } -#endregion + #endregion -#region TestHelper + #region TestHelper public class TestHelper { public static string GetProgramAbsolutePath() @@ -165,6 +174,17 @@ public static string GetProgramAbsolutePath() return path; } } -#endregion + #endregion + + #region LogEnvironment + [TestMethod] + public void LogEnvironment() + { + StringBuilder b = new StringBuilder(); + b.AppendLine($"OS = {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}"); + b.AppendLine($"Dotnet Runtime = {RuntimeInformation.FrameworkDescription} {RuntimeInformation.ProcessArchitecture}"); + Console.WriteLine(b.ToString()); + } + #endregion } } diff --git a/Joveler.DynLoader.Tests/runtimes/win-arm64/native/zlib1.dll b/Joveler.DynLoader.Tests/runtimes/win-arm64/native/zlib1.dll new file mode 100644 index 0000000..cebffed Binary files /dev/null and b/Joveler.DynLoader.Tests/runtimes/win-arm64/native/zlib1.dll differ diff --git a/Joveler.DynLoader.Tests/runtimes/win-x64/native/zlib1.dll b/Joveler.DynLoader.Tests/runtimes/win-x64/native/zlib1.dll new file mode 100644 index 0000000..134c697 Binary files /dev/null and b/Joveler.DynLoader.Tests/runtimes/win-x64/native/zlib1.dll differ diff --git a/Joveler.DynLoader.Tests/runtimes/win-x86/native/zlib1.dll b/Joveler.DynLoader.Tests/runtimes/win-x86/native/zlib1.dll new file mode 100644 index 0000000..b1ab8dd Binary files /dev/null and b/Joveler.DynLoader.Tests/runtimes/win-x86/native/zlib1.dll differ diff --git a/Joveler.DynLoader/DynLoaderBase.cs b/Joveler.DynLoader/DynLoaderBase.cs index 82af4e0..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 @@ -24,10 +24,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ using System; -#if !NETCOREAPP3_1 +#if !NETCOREAPP using System.ComponentModel; // For Win32Exception +using System.IO; // For Path #endif -using System.IO; using System.Runtime.InteropServices; using System.Text; @@ -35,6 +35,10 @@ namespace Joveler.DynLoader { public abstract class DynLoaderBase : IDisposable { + #region Fields and Properties + protected string LibPath { get; private set; } = string.Empty; + #endregion + #region Constructor /// /// Create an instance of DynLoaderBase and set platform conventions. @@ -129,7 +133,7 @@ private void GlobalCleanup() /// public void LoadLibrary() { - LoadLibrary(null); + LoadLibrary(null, null); } /// @@ -137,6 +141,25 @@ public void LoadLibrary() /// /// A native library file to load. public void LoadLibrary(string libPath) + { + LoadLibrary(libPath, null); + } + + /// + /// Load a native dynamic library from a path of `DefaultLibFileName`, with custom object. + /// + /// Custom object has been passed to . + public void LoadLibrary(object loadData) + { + LoadLibrary(null, 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) { // Should DynLoaderBase use default library filename? if (libPath == null) @@ -146,9 +169,13 @@ public void LoadLibrary(string libPath) libPath = DefaultLibFileName; } + LibPath = libPath; + + // Parse custom load data + HandleLoadData(loadData); // Use .NET Core's NativeLibrary when available -#if NETCOREAPP3_1 +#if NETCOREAPP // NET's NativeLibrary will throw DllNotFoundException by itself. // No need to check _hModule.IsInvalid here. _hModule = new NetSafeLibHandle(libPath); @@ -172,7 +199,7 @@ public void LoadLibrary(string libPath) if (_hModule.IsInvalid) { // Sample message of .NET Core 3.1's NativeLoader: - // Unable to load DLL 'x64\zlibwapi.dll' or one of its dependencies: The specified module could not be found. (0x8007007E). + // Unable to load DLL 'x64\zlibwapi.dll' or one of its dependencies: The specified module could not be found. (0x8007007E) // Unable to load DLL 'ᄒᆞᆫ글ḀḘ韓國Ghost.dll' or one of its dependencies: 지정된 모듈을 찾을 수 없습니다. (0x8007007E) string exceptMsg = $"Unable to load DLL '{libPath}' or one of its dependencies"; int errorCode = Marshal.GetLastWin32Error(); @@ -240,13 +267,14 @@ public void LoadLibrary(string libPath) private bool Loaded => _hModule != null && !_hModule.IsInvalid; #endregion - #region (protected) GetFuncPtr + #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 { string funcSymbol = typeof(T).Name; @@ -254,15 +282,16 @@ 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 { IntPtr funcPtr; -#if NETCOREAPP3_1 +#if NETCOREAPP funcPtr = NativeLibrary.GetExport(_hModule.DangerousGetHandle(), funcSymbol); #else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -288,7 +317,6 @@ protected T GetFuncPtr(string funcSymbol) where T : Delegate throw new EntryPointNotFoundException($"{exceptMsg}."); else throw new EntryPointNotFoundException($"{exceptMsg}: {errorMsg}"); - } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) @@ -311,9 +339,52 @@ protected T GetFuncPtr(string funcSymbol) where T : Delegate throw new PlatformNotSupportedException(); } #endif - 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; +#if NETCOREAPP + if (!NativeLibrary.TryGetExport(_hModule.DangerousGetHandle(), funcSymbol, out funcPtr)) + { // symbol not found + funcPtr = IntPtr.Zero; + } +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + funcPtr = NativeMethods.Win32.GetProcAddress(_hModule, funcSymbol); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + funcPtr = NativeMethods.Linux.DLSym(_hModule, funcSymbol); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + funcPtr = NativeMethods.Mac.DLSym(_hModule, funcSymbol); + } + else + { + throw new PlatformNotSupportedException(); + } +#endif + 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; + } #endregion #region (abstract) DefaultLibFileName @@ -327,6 +398,17 @@ protected T GetFuncPtr(string funcSymbol) where T : Delegate protected abstract string DefaultLibFileName { get; } #endregion + #region (virtual) HandleLoadData + /// + /// Handle custom object passed into . + /// + /// Custom object has been passed to . + protected virtual void HandleLoadData(object data) + { + + } + #endregion + #region (abstract) LoadFunctions, ResetFunctions /// /// Load native functions with a GetFuncPtr. Called in the constructors. diff --git a/Joveler.DynLoader/Joveler.DynLoader.csproj b/Joveler.DynLoader/Joveler.DynLoader.csproj index 745366c..37bc163 100644 --- a/Joveler.DynLoader/Joveler.DynLoader.csproj +++ b/Joveler.DynLoader/Joveler.DynLoader.csproj @@ -1,27 +1,30 @@  - net451;netstandard2.0;netcoreapp3.1 + net46;netstandard2.0;netcoreapp3.1 netstandard2.0;netcoreapp3.1 Joveler.DynLoader Joveler.DynLoader - 2.1.1 + 2.2.0 Hajin Jang Joveler Cross-platform native dynamic library loader for .NET. Provides advanced P/Invoke functionality using NativeLibrary, LoadLibrary and libdl. Supports Windows, Linux, and macOS. - Copyright (C) 2019-2021 Hajin Jang + Copyright (C) 2019-2023 Hajin Jang MIT https://github.com/ied206/Joveler.DynLoader images\Logo.png https://github.com/ied206/Joveler.DynLoader - - Official support for ARM64 macOS. -- Unify .NET Framework 4.5.1 codebase and .NET Standard 2.0 codebase. + - 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 976f036..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 @@ -148,23 +148,42 @@ protected virtual void PostDisposeHook() { } /// public void GlobalInit() { - GlobalInit(null); + GlobalInit(null, null); } /// /// Create DynLoaderBase singleton instance in a thread-safe way. /// - /// + /// A native library file to load. public void GlobalInit(string libPath) + { + GlobalInit(libPath, null); + } + + /// + /// Create DynLoaderBase singleton instance in a thread-safe way, with a custom object. + /// + /// Custom object to be passed to . + public void GlobalInit(object loadData) + { + GlobalInit(null, loadData); + } + + /// + /// 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 loadData) { lock (_loadLock) { if (Lib != null) throw new InvalidOperationException(ErrorMsgAlreadyLoaded); - PreInitHook(); Lib = CreateLoader(); - Lib.LoadLibrary(libPath); + PreInitHook(); + Lib.LoadLibrary(libPath, loadData); PostInitHook(); } } diff --git a/Joveler.DynLoader/NativeMethods.cs b/Joveler.DynLoader/NativeMethods.cs index 3f277e5..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 @@ -24,7 +24,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ using System; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Joveler.DynLoader 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 bcf70f2..9976e8d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -2,16 +2,16 @@ `Joveler.DynLoader` is a cross-platform native dynamic library loader for .NET. It allows developers to create a wrapper of native C libraries easily. -The library provide two abstract class, [DynLoaderBase](#DynLoaderBase) and [LoadManagerBase](#LoadManagerBase). +The library provides two abstract class, [DynLoaderBase](#DynLoaderBase) and [LoadManagerBase](#LoadManagerBase). -Please also read [P/Invoke Tips from DynLoader](#Tips) for your easy P/Invoke life. +Please also read [P/Invoke Tips from DynLoader](#Tips) for easy P/Invoke life. ## Getting Started -To use DynLoader, you should know about two classes. +To use DynLoader, you should learn about two main classes. -| Class | 역할 | -|--------------|------| +| Class | Description | +|--------------|-------------| | [DynLoaderBase](#DynLoaderBase) | Scaffold of a native library wrapper. | | [LoadManagerBase](#LoadManagerBase) | Manages `DynLoaderBase` singleton instance. | @@ -37,7 +37,7 @@ Follow these steps to create a wrapper of a native library. Follow these steps to use a wrapper library. -1. Make an interface which calls `LoadManagerBase.GlobalInit()`. +1. Make an interface that calls `LoadManagerBase.GlobalInit()`. ```csharp public class Magic : IDisposable { @@ -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 @@ -59,14 +60,14 @@ Inherit [DynLoaderBase](./Joveler.DynLoader/DynLoaderBase.cs) to create a wrappe **Example Files** -[Joveler.DynLoader.Tests](./Joveler.DynLoader.Tests) contains simplified wrappers of [zlib](https://www.zlib.net) and [libmagic](http://www.darwinsys.com/file/) as examples. Freely adapt them as you need, they are released as public domain. +[Joveler.DynLoader.Tests](./Joveler.DynLoader.Tests) contains simplified wrappers of [zlib](https://www.zlib.net) and [libmagic](http://www.darwinsys.com/file/) as examples. Freely adapt them as you need, as they are released as public domain. - zlib : [SimpleZLib.cs](./Joveler.DynLoader.Tests/SimpleZLib.cs) - magic : [SimpleFileMagic.cs](./Joveler.DynLoader.Tests/SimpleFileMagic.cs) The test project also showcases per-platform delegate declarations. Read [SimplePlatform.cs](./Joveler.DynLoader.Tests/SimplePlatform.cs). -### Delegate of native functions +### Delegate native functions You need to provide a prototype of the native functions, similar to traditional [DllImport P/Invoke](https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke). @@ -105,9 +106,37 @@ public SimpleFileMagic() : base() { } ### LoadLibrary -After creating an instance of a derived class, make sure to call `LoadLibrary()` to load a native library. After then you can invoke extern native functions via delegate instances. +```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); +``` -`LoadLibrary(string libPath)` loads that specific native library. The parameterless version loads the default native library from the base system. +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. + +| 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. @@ -119,11 +148,11 @@ Under the hood, DynLoader calls [LoadLibraryEx](https://docs.microsoft.com/en-us DynLoader follows the OS's library resolving order. On Windows, it follows [alternative library search order](https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#alternate-search-order-for-desktop-applications). On POSIX, it follows the order explained on [dlopen manual](http://man7.org/linux/man-pages/man3/dlopen.3.html). -### Methods and property to override +### Methods and properties to override ```csharp /// -/// Default filename of the native libary to use. Override only if the target platform ships with the native library. +/// Default filename of the native library to use. Override only if the target platform ships with the native library. /// /// /// Throw PlatformNotSupportedException optionally when the library is included only in some of the target platforms. @@ -138,13 +167,18 @@ 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() You must override `LoadFunctions()` with a code loading delegate of native functions. -Call `GetFuncPtr(string funcSymbol)` with a delegate type (`T`) and function symbol name (`funcSymbol`) to get a C# delegate of a symbol. Assign return value as a delegate instance you previously declared. +Call `GetFuncPtr(string funcSymbol)` with a delegate type (`T`) and function symbol name (`funcSymbol`) to get a C# delegate of a symbol. Assign the return value as a delegate instance you previously declared. The parameterless `GetFuncPtr()` is a slow but more convenient variant. It uses reflection (`typeof(T).Name`) to get a real name of `T` at runtime. If your target platform restricts the use of reflection, do not use it. @@ -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 it. +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. @@ -222,7 +313,7 @@ public enum PlatformDataModel ILP32 = 2, } /// -/// Size of the long type of the platform. +/// Size of the long type of platform. /// public enum PlatformLongSize { @@ -255,7 +346,7 @@ public enum PlatformBitness /// Default unicode encoding convention of the platform. /// /// -/// Some native libraries does not follow default unicode encoding convention of the platform, so be careful. +/// Some native libraries do not follow the default Unicode encoding convention of the platform, be careful. /// public enum UnicodeConvention { @@ -278,7 +369,7 @@ public PlatformUnicodeConvention PlatformUnicodeConvention { get; } public Encoding PlatformUnicodeEncoding { get; } /// -/// Convert buffer pointer to string following platform's default encoding convention. Wrapper of Marshal.PtrToString*(). +/// Convert buffer pointer to string following the platform's default encoding convention. The wrapper of Marshal.PtrToString*(). /// /// /// Marshal.PtrToStringAnsi() use UTF-8 on POSIX. @@ -288,7 +379,7 @@ public Encoding PlatformUnicodeEncoding { get; } public string PtrToStringAuto(IntPtr ptr); /// /// -/// Convert string to buffer pointer following platform's default encoding convention. Wrapper of Marshal.StringToHGlobal*(). +/// Convert string to buffer pointer following the platform's default encoding convention. The wrapper of Marshal.StringToHGlobal*(). /// /// /// Marshal.StringToHGlobalAnsi() use UTF-8 on POSIX. @@ -296,10 +387,10 @@ public string PtrToStringAuto(IntPtr ptr); /// String to convert /// IntPtr of the string buffer. You must call Marshal.FreeHGlobal() with return value to prevent memory leak. public IntPtr StringToHGlobalAuto(string str); -/// Convert string to buffer pointer following platform's default encoding convention. Wrapper of Marshal.StringToCoTaskMem*(). +/// Convert string to buffer pointer following the platform's default encoding convention. The wrapper of Marshal.StringToCoTaskMem*(). /// /// -/// Marshal.StringToCoTaskMemAnsi() use UTF-8 on POSIX. +/// Marshal.StringToCoTaskMemAnsi() uses UTF-8 on POSIX. /// /// String to convert /// IntPtr of the string buffer. You must call Marshal.FreeCoTaskMem() with return value to prevent memory leak. @@ -324,9 +415,9 @@ In C language, the size of a data type may change per target platform. It is cal | `PlatformBitness` | `Bit32` | `Bit64` | | Size of the `UIntPtr` | 32bit | 64bit | -It is useful when to have to write different code per bitness or handle marshaling of `size_t`. +It is useful when have to write different code per bitness or handle marshaling of `size_t`. -`size_t` can be represented as `UIntPtr` in P/Invoke signatures. .NET make sure that `UIntPtr` does not store the value larger than the platform's bit size. For example, assigning `ulong.MaxValue` to `UIntPtr` on 32bit platforms invoke `OverflowException`. +`size_t` can be represented as `UIntPtr` in P/Invoke signatures. .NET makes sure that `UIntPtr` does not store the value larger than the platform's bit size. For example, assigning `ulong.MaxValue` to `UIntPtr` on 32bit platforms invoke `OverflowException`. #### PlatformUnicodeConvention, PlatformUnicodeEncoding @@ -337,9 +428,9 @@ Windows often use UTF-16 LE, while many POSIX libraries use UTF-8 without BOM. | `UnicodeConvention` | `Utf16` | `Utf8` | | `UnicodeEncoding` | `Encoding.UTF16` (UTF-16 LE) | `new UTF8Encoding(false)` (UTF-8 without BOM) | -`string PtrToStringAuto(IntPtr ptr)`, `IntPtr StringToHGlobalAuto(string str)` and `IntPtr StringToCoTaskMemAuto(string str)` is a wrapper methods of `Marshal.PtrToString*` and `Marshal.StringTo*`. They decide which encoding to use automatically depending on value of `UnicodeConvention` property. +`string PtrToStringAuto(IntPtr ptr)`, `IntPtr StringToHGlobalAuto(string str)` and `IntPtr StringToCoTaskMemAuto(string str)` is a wrapper methods of `Marshal.PtrToString*` and `Marshal.StringTo*`. They decide which encoding to use automatically depending on the value of the `UnicodeConvention` property. -**WARNING**: Native libraries may not follow the platform's default Unicode encoding convention! It is your responsibility to check which encoding library is using. For example, some cross-platform libraries which originated from the POSIX world do not use `wchar_t`, effectively using `ANSI` encoding on Windows instead of `UTF-16`. That is why you can overwrite the `UnicodeConvention` value after the class was initialized. +**WARNING**: Native libraries may not follow the platform's default Unicode encoding convention! It is your responsibility to check which encoding library is used. For example, some cross-platform libraries which originated from the POSIX world do not use `wchar_t`, effectively using `ANSI` encoding on Windows instead of `UTF-16`. That is why you can overwrite the `UnicodeConvention` value after the class was initialized. ### Disposable Pattern @@ -347,7 +438,7 @@ The class implements [Disposable Pattern](https://docs.microsoft.com/en-us/dotne ## LoadManagerBase -[LoadManagerBase](./Joveler.DynLoader/LoadManagerBase.cs) class provides a thread-safe way to manage `DynLoaderBase` singleton instance. +[LoadManagerBase](./Joveler.DynLoader/LoadManagerBase.cs) class provides a thread-safe way to manage the `DynLoaderBase` singleton instance. ### Methods and properties to override @@ -400,14 +491,14 @@ These hooks will be called before/after `CreateLoader()`/`Dispose()`. Implementi ```csharp /// -/// Allocate other external resources before CreateLoader get called. +/// Allocate other external resources before CreateLoader gets called. /// /// /// Called in GlobalInit() and GlobalInit(string libPath). /// protected virtual void PreInitHook() { } /// -/// Allocate other external resources after CreateLoader get called. +/// Allocate other external resources after CreateLoader gets called. /// /// /// Called in GlobalInit() and GlobalInit(string libPath). @@ -421,7 +512,7 @@ protected virtual void PostInitHook() { } /// protected virtual void PreDisposeHook() { } /// -/// Disallocate other external resources after disposing DynLoaderBase instance. +/// Disallocate other external resources after disposing of DynLoaderBase instance. /// /// /// Called in GlobalCleanup(). @@ -435,7 +526,7 @@ protected virtual void PostDisposeHook() { } When your app depends on user libraries, not a Win32 API, or a system call, you have to bundle the libraries yourself. -#### 1) Set `Copy to Output Directory` property of native binaries. +#### 1) Set the `Copy to Output Directory` property of native binaries. Add native library files into the project, and set `Copy to Output Directory` to `Copy if newer` in their property. @@ -448,6 +539,9 @@ Add native library files into the project, and set `Copy to Output Directory` to PreserveNewest + + PreserveNewest + ``` @@ -477,7 +571,7 @@ This method does not work on application projects. For the .NET Framework NuGet package, write an MSBuild script to handle native libraries. -**Example**: Add MSBuild script [SampleScript.netfx.targets](./Joveler.DynLoader.Tests/SampleScript.netfx.targets) to the project directory. Also add this line to .csproj: +**Example**: Add MSBuild script [SampleScript.netfx.targets](./Joveler.DynLoader.Tests/SampleScript.netfx.targets) to the project directory. Also, add this line to .csproj: ```xml ``` @@ -489,12 +583,12 @@ You can freely adapt [SampleScript.netfx.targets](./Joveler.DynLoader.Tests/Samp This is the snippet extracted from the sample .csproj file. - (1) Use `Copy to Output Directory` for application build. -- (2) Create standard NuGet package layout for .NET Core nupkg. +- (2) Create a standard NuGet package layout for .NET Core nupkg. - (3) Use MSBuild scripts for .NET Framework nupkg. ```xml - - + + x86\%(FileName)%(Extension) PreserveNewest @@ -503,6 +597,10 @@ This is the snippet extracted from the sample .csproj file. x64\%(FileName)%(Extension) PreserveNewest + + arm64\%(FileName)%(Extension) + PreserveNewest + @@ -534,6 +632,10 @@ This is the snippet extracted from the sample .csproj file. runtimes\osx-x64\native\%(FileName)%(Extension) PreserveNewest + + runtimes\osx-arm64\native\%(FileName)%(Extension) + PreserveNewest + @@ -545,6 +647,7 @@ This is the snippet extracted from the sample .csproj file. + @@ -556,14 +659,14 @@ Multiple calling conventions are used following the target OS and architecture. **Recommended Workaround**: Always set calling a convention for `x86`, as they are ignored in the other architectures. -#### x86 +#### x86/i686 On x86, you need to be cautious of calling conventions. - Windows: Win32 APIs use stdcall, while the user libraries selectively use cdecl or stdcall. - Linux, macOS: Every function uses cdecl. -Many libraries originated from the POSIX world often exclusively use cdecl. It is still valid on Windows when the library is cross-platform. In that case, specify `CallingConvention.Cdecl`. +Many libraries originating from the POSIX world often exclusively use cdecl. It is still valid on Windows when the library is cross-platform. In that case, specify `CallingConvention.Cdecl`. ```csharp [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -575,13 +678,13 @@ Similarly, if you are writing a wrapper of Win32 APIs on Windows, specify `Calli [UnmanagedFunctionPointer(CallingConvention.StdCall)] ``` -Some cross-platform libraries use stdcall on Windows and cdecl on POSIX (e.g., zlib), however. In that case, specify `CallingConvention.Winapi`. stdcall is automatically used on Windows while the cdecl is used on POSIX. +Some cross-platform libraries use stdcall on Windows and cdecl on POSIX (e.g., `zlibwapi.dll` build of zlib), however. In that case, specify `CallingConvention.Winapi`. stdcall is automatically used on Windows while the cdecl is used on POSIX. ```csharp [UnmanagedFunctionPointer(CallingConvention.Winapi)] ``` -#### x64 +#### x64/amd64 On x64, every platform enforces using the standardized fastcall convention. So, in theory, you do not need to care about it. @@ -600,7 +703,7 @@ Similar to x64, these platforms are known to enforce one standardized calling co `size_t` has a different size per architecture. It has the same size as the pointer size, using 4B on 32bit arch (x86, armhf) and using 8B on 64bit arch (x64, arm64). It is troublesome in cross-platform P/Invoke, as no direct counterpart exists in .NET. -You can exploit [UIntPtr](https://docs.microsoft.com/en-US/dotnet/api/system.uintptr) (or [IntPtr](https://docs.microsoft.com/en-US/dotnet/api/system.intptr)) struct to handle this problem. While the .NET runtime does not provide the direct mechanism, these struct has the same size as the platform's pointer size. Thus, we can safely use `UIntPtr` as the C# equivalent of `size_t`. You must have to take caution, though, because we want to use `UIntPtr` as a value, not an address. +You can exploit [UIntPtr](https://docs.microsoft.com/en-US/dotnet/api/system.uintptr) (or [IntPtr](https://docs.microsoft.com/en-US/dotnet/api/system.intptr)) struct to handle this problem. While the .NET runtime does not provide the direct mechanism, this struct has the same size as the platform's pointer size. Thus, we can safely use `UIntPtr` as the C# equivalent of `size_t`. You must have to take caution, though, because we want to use `UIntPtr` as a value, not an address. I recommend using `UIntPtr` instead of `IntPtr` to represent `size_t` for safety. `IntPtr` is often used as a pure pointer itself while the `UIntPtr` is rarely used. Distinguishing `UIntPtr (value)` from the `IntPtr (address)` prevents the mistakes and crashes from confusing these two. @@ -622,7 +725,7 @@ C\# 9.0 or later supports [`nint` and `nuint`](https://docs.microsoft.com/en-us/ ```csharp [UnmanagedFunctionPointer(CallingConvention.Cdecl)] -internal delegate UIntPtr LZ4F_getFrameInfo( +internal delegate nuint LZ4F_getFrameInfo( IntPtr dctx, FrameInfo frameInfoPtr, IntPtr srcCapacity, @@ -634,9 +737,9 @@ internal static LZ4F_getFrameInfo GetFrameInfo; **Recommended Workaround**: If the native library use `long` in its APIs, declare two sets of delegates, the LP64 model for POSIX 64bit and LLP64 for the other. -In 64bit, `long` can have different sizes per target OS and architecture. Windows use the LLP64 data model (long is 32bit) on 64bit arch, while the POSIX use LP64 (long is 64bit). +In 64bit, `long` can have different sizes per target OS and architecture. Windows uses the LLP64 data model (long is 32bit) on 64bit arch, while the POSIX use LP64 (long is 64bit). -If a native library uses `long` in the exported functions, there is no simple solution. You would have to prepare two sets of delegates, and make sure you assign and call the right delegate per target architecture and OS. +If a native library uses `long` in the exported functions, there is no simple solution. You would have to prepare two sets of delegates and make sure you assign and call the right delegate per target architecture and OS. Some libraries with a long history (e.g., zlib) have this problem. Fortunately, many modern cross-platform libraries tend to use types of `` or similar so that they can ensure stable type size across platforms. @@ -644,9 +747,9 @@ Some libraries with a long history (e.g., zlib) have this problem. Fortunately, ### String encoding -**Recommended Workaround**: Declare two sets of delegates, the UTF-16 model for POSIX 64bit and LLP64 for the other. +**Recommended Workaround**: Use the `IntPtr` type, and convert it to/from a string in runtime with helper methods. -Different platforms have different charset and encoding conventions, and native libraries often follow it. +Different platforms have different charset and encoding conventions, and native libraries often follow them. - Windows: `UTF-16`, `ANSI` - POSIX: `UTF-8` @@ -654,13 +757,18 @@ Different platforms have different charset and encoding conventions, and native Look for which data type the library used for strings. - `char*`: `ANSI` on Windows and `UTF-8` on POSIX. Mostly used in POSIX libraries. -- `wchar_t*`: `UTF-16` on Windows and `UTF-32`on POSIX. Windows libraries use it but rarely in POSIX libraries. +- `wchar_t*`: `UTF-16` on Windows and `UTF-32` on POSIX. Windows libraries use it but rarely in POSIX libraries. - `tchar*`: `UTF-16` on Windows and `UTF-8` on POSIX. Windows libraries and some cross-platform POSIX libraries use it. Fortunately, you do not need to duplicate structs in most cases. Put `IntPtr` in place of a string field, then return string as a property using `DynLoaderBase.StringTo*Auto()` and `DynLoaderBase.PtrToStringAuto()` helper methods. **Example** +This example shows two solutions: + +- Declaring two sets of delegates +- Use `IntPtr` and convert them in runtime. + ```csharp internal class Utf8d {