Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
ied206 committed Sep 3, 2023
2 parents b0012fd + 01a35bd commit 24079d5
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## v2.x

### v2.3.0

Released on 2023-09-03.

- Add `LoadManagerBase<T>.TryGlobalCleanup()`.
- It is useful when you want to ensure that there is no loaded native library in the manager.

### v2.2.1

Released on 2023-08-28.
Expand Down
64 changes: 55 additions & 9 deletions Joveler.DynLoader.Tests/SimpleZLibTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,17 @@ public void CdeclCreateDispose()
public void StdcallManager()
{
string libPath = TestSetup.PackagedStdcallZLibPath;
ManagerTemplate(libPath, true);
ManagerTemplate(libPath, true, true);
}

[TestMethod]
public void CdeclManager()
{
string libPath = TestSetup.PackagedCdeclZLibPath;
ManagerTemplate(libPath, false);
ManagerTemplate(libPath, false, false);
}

private static void ManagerTemplate(string libPath, bool isWindowsStdcall)
private static void ManagerTemplate(string libPath, bool isWindowsStdcall, bool useTryCleanup)
{
SimpleZLibManager manager = new SimpleZLibManager();
SimpleZLibLoadData loadData = new SimpleZLibLoadData()
Expand Down Expand Up @@ -171,16 +171,30 @@ private static void ManagerTemplate(string libPath, bool isWindowsStdcall)
Assert.AreNotEqual(IntPtr.Zero, manager.Lib.DeflateRawPtr);

bool dupCleanGuard = false;
manager.GlobalCleanup();
try
if (useTryCleanup)
{
manager.GlobalCleanup();
Assert.IsTrue(manager.Loaded);
Assert.IsTrue(manager.TryGlobalCleanup());
Assert.IsFalse(manager.Loaded);
Assert.IsFalse(manager.TryGlobalCleanup());
Assert.IsFalse(manager.Loaded);
}
catch (InvalidOperationException)
else
{
dupCleanGuard = true;
Assert.IsTrue(manager.Loaded);
manager.GlobalCleanup();
Assert.IsFalse(manager.Loaded);
try
{
manager.GlobalCleanup();
}
catch (InvalidOperationException)
{
dupCleanGuard = true;
}
Assert.IsTrue(dupCleanGuard);
Assert.IsFalse(manager.Loaded);
}
Assert.IsTrue(dupCleanGuard);
}

[TestMethod]
Expand Down Expand Up @@ -209,6 +223,38 @@ public void DllNotFoundRetry()
manager.GlobalInit(existLibPath, loadData);
}
Assert.IsTrue(catched);

if (manager.Loaded)
manager.GlobalCleanup();
}

[TestMethod]
public void TryGlobalCleanup()
{
string existLibPath = TestSetup.PackagedCdeclZLibPath;
string libDir = Path.GetDirectoryName(existLibPath);
string notExistLibPath = Path.Combine(libDir, "404_NOT_FOUND.dll");

SimpleZLibManager manager = new SimpleZLibManager();
SimpleZLibLoadData loadData = new SimpleZLibLoadData()
{
IsWindowsStdcall = false,
};

bool catched = false;
try
{
manager.GlobalInit(notExistLibPath, loadData);
}
catch (DllNotFoundException)
{
catched = true;
}
Assert.IsTrue(catched);

// LoadManager must not ne loaded after throwing DllNotFoundException
Assert.IsFalse(manager.Loaded);
Assert.IsFalse(manager.TryGlobalCleanup());
}
}
}
7 changes: 4 additions & 3 deletions Joveler.DynLoader/Joveler.DynLoader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">netstandard2.0;netcoreapp3.1</TargetFrameworks>
<PackageId>Joveler.DynLoader</PackageId>
<Title>Joveler.DynLoader</Title>
<Version>2.2.1</Version>
<Version>2.3.0</Version>
<Authors>Hajin Jang</Authors>
<Company>Joveler</Company>
<Description>Cross-platform native dynamic library loader for .NET.
Expand All @@ -15,8 +15,9 @@ Supports Windows, Linux, and macOS.</Description>
<PackageProjectUrl>https://github.com/ied206/Joveler.DynLoader</PackageProjectUrl>
<PackageIcon>images\Logo.png</PackageIcon>
<RepositoryUrl>https://github.com/ied206/Joveler.DynLoader</RepositoryUrl>
<PackageReleaseNotes>- Fix failing second call of GlobalInit() if the first call throwed an exception.</PackageReleaseNotes>
<PackageReleaseNotes>- Add LoadManagerBase.TryGlobalCleanup().</PackageReleaseNotes>
<PackageTags>native pinvoke interop dynamic library loader dll so dylib</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<!-- PackageReference -->
<ItemGroup>
Expand All @@ -25,7 +26,7 @@ Supports Windows, Linux, and macOS.</Description>
<!-- NuGet Pacakge -->
<ItemGroup>
<!-- Nuget README -->
<None Include="NUGET_README.md" Pack="true" PackagePath="\README.md"/>
<None Include="NUGET_README.md" Pack="true" PackagePath="\README.md" />
<!-- NuGet Pacakge Icon -->
<None Include="..\Image\Logo.png" Pack="true" PackagePath="images\" />
</ItemGroup>
Expand Down
71 changes: 61 additions & 10 deletions Joveler.DynLoader/LoadManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public abstract class LoadManagerBase<T> where T : DynLoaderBase
#endregion

#region Thread-Safe Load Lock Management
/// <summary>
/// Lock object for thread synchronization.
/// </summary>
private readonly object _loadLock = new object();
/// <summary>
/// Is the library loaded?
Expand All @@ -69,7 +72,7 @@ public bool Loaded
}

/// <summary>
/// Ensure that the library have been loaded.
/// Ensure that the library has been loaded.
/// </summary>
public void EnsureLoaded()
{
Expand All @@ -81,7 +84,7 @@ public void EnsureLoaded()
}

/// <summary>
/// Ensure that the library have not been loaded yet.
/// Ensure that the library has not been loaded yet.
/// </summary>
public void EnsureNotLoaded()
{
Expand All @@ -93,7 +96,7 @@ public void EnsureNotLoaded()
}
#endregion

#region Init and Cleanup Methods
#region CreateLoader
/// <summary>
/// Represents parameter-less constructor of DynLoaderBase.
/// </summary>
Expand All @@ -103,7 +106,7 @@ public void EnsureNotLoaded()
/// <returns>DynLoaderBase instace</returns>
protected abstract T CreateLoader();
/// <summary>
/// Represents constructor of DynLoaderBase with a `libPath` parameter.
/// (DEPRECATED) Represents constructor of DynLoaderBase with a `libPath` parameter.
/// </summary>
/// <remarks>
/// Called in GlobalInit(string libPath).
Expand All @@ -114,15 +117,18 @@ protected virtual T CreateLoader(string libPath)
{
return CreateLoader();
}
#endregion

#region Hooks
/// <summary>
/// Allocate other external resources before CreateLoader get called.
/// Allocate other external resources before CreateLoader() get called.
/// </summary>
/// <remarks>
/// Called in GlobalInit().
/// </remarks>
protected virtual void PreInitHook() { }
/// <summary>
/// Allocate other external resources after CreateLoader get called.
/// Allocate other external resources after CreateLoader() get called.
/// </summary>
/// <remarks>
/// Called in GlobalInit().
Expand All @@ -142,10 +148,15 @@ protected virtual void PreDisposeHook() { }
/// Called in GlobalCleanup().
/// </remarks>
protected virtual void PostDisposeHook() { }
#endregion

#region GlobalInit
/// <summary>
/// Create DynLoaderBase singleton instance in a thread-safe way.
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException when a native library has already been loaded.
/// </remarks>
public void GlobalInit()
{
GlobalInit(null, null);
Expand All @@ -154,6 +165,9 @@ public void GlobalInit()
/// <summary>
/// Create DynLoaderBase singleton instance in a thread-safe way.
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException when a native library has already been loaded.
/// </remarks>
/// <param name="libPath">A native library file to load.</param>
public void GlobalInit(string libPath)
{
Expand All @@ -163,6 +177,9 @@ public void GlobalInit(string libPath)
/// <summary>
/// Create DynLoaderBase singleton instance in a thread-safe way, with a custom object.
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException when a native library has already been loaded.
/// </remarks>
/// <param name="loadData">Custom object to be passed to <see cref="DynLoaderBase{T}.ParseLoadData()"/>.</param>
public void GlobalInit(object loadData)
{
Expand All @@ -172,6 +189,9 @@ public void GlobalInit(object loadData)
/// <summary>
/// Create DynLoaderBase singleton instance in a thread-safe way, with a custom object.
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException when a native library has already been loaded.
/// </remarks>
/// <param name="libPath">A native library file to load.</param>
/// <param name="loadData">Custom object to be passed to <see cref="DynLoaderBase{T}.ParseLoadData()"/>.</param>
public void GlobalInit(string libPath, object loadData)
Expand All @@ -195,23 +215,54 @@ public void GlobalInit(string libPath, object loadData)
}
}
}
#endregion

#region GlobalCleanup
/// <summary>
/// Dispose DynLoaderBase singleton instance in a thread-safe way.
/// </summary>
/// <remarks>
/// Throws an InvalidOperationException when a native library has not been loaded.
/// </remarks>
public void GlobalCleanup()
{
lock (_loadLock)
{
if (Lib == null)
throw new InvalidOperationException(ErrorMsgInitFirst);

PreDisposeHook();
Lib.Dispose();
PostDisposeHook();
Lib = null;
InternalCleanup();
}
}

/// <summary>
/// Dispose DynLoaderBase singleton instance in a thread-safe way.
/// </summary>
/// <remarks>
/// Use this method to make sure that there is no loaded native library.
/// </remarks>
/// <returns>
/// Returns true when succeeded to unload, and false when a native library has not been loaded.
/// </returns>
public bool TryGlobalCleanup()
{
lock (_loadLock)
{
if (Lib == null)
return false;

InternalCleanup();
return true;
}
}

private void InternalCleanup()
{
PreDisposeHook();
Lib.Dispose();
PostDisposeHook();
Lib = null;
}
#endregion
}
}

0 comments on commit 24079d5

Please sign in to comment.