diff --git a/ReFuel.StbImage.csproj b/ReFuel.StbImage.csproj
index 7026d6a..811403e 100644
--- a/ReFuel.StbImage.csproj
+++ b/ReFuel.StbImage.csproj
@@ -14,7 +14,7 @@
True
ReFuel.StbImage
- 2.0.2-rc.1
+ 2.1.0-rc.0
STBI Authors, H. Utku Maden
A C# wrapper for the ubiquitous stb_image.h and stb_image_write.h library.
@@ -26,10 +26,10 @@
https://git.mixedup.dev/ReFuel/ReFuel.StbImage
git
stb; stb_image; stbi; image; load; save; read; write
- # 2.0.2
+ # 2.1.0 (ABI BRAKING)
* Fixed calling convention of unmanaged function pointers. (Thanks NogginBops!)
-* Allocating a GC handle to StbiStreamWrapper class and passing it as userdata into stbi in order to prevent
- the object from being prematurely collected by the garbage collector when optimizations are enabled in Release mode.
+* Modified StbiStreamWrapper in order to fixed backing delegates of function pointers from being prematurely collected
+ by release mode JIT and the GC. StbiStreamWrapper.Callbacks is now a readonly field. (ABI BREAKING)
# 2.0.1
* Enabled optimizations across the board for native and managed assemblies.
@@ -56,7 +56,7 @@
runtimes/linux-arm64/native/
true
- PreserveNewest/
+ PreserveNewest
runtimes/linux-x64/native/
diff --git a/StbImage.cs b/StbImage.cs
index fc57153..28a3f88 100644
--- a/StbImage.cs
+++ b/StbImage.cs
@@ -209,19 +209,19 @@ namespace ReFuel.Stb
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
- GCHandle gch = GCHandle.Alloc(wrapper, GCHandleType.Normal);
- wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
IntPtr imagePtr;
- if (asFloat)
+ fixed (stbi_io_callbacks* cb = &wrapper.Callbacks)
{
- imagePtr = (IntPtr)Stbi.loadf_from_callbacks(&cb, (void*)(IntPtr)gch, &x, &y, &iFormat, (int)format);
+ if (asFloat)
+ {
+ imagePtr = (IntPtr)Stbi.loadf_from_callbacks(cb, null, &x, &y, &iFormat, (int)format);
+ }
+ else
+ {
+ imagePtr = (IntPtr)Stbi.load_from_callbacks(cb, null, &x, &y, &iFormat, (int)format);
+ }
}
- else
- {
- imagePtr = (IntPtr)Stbi.load_from_callbacks(&cb, (void*)(IntPtr)gch, &x, &y, &iFormat, (int)format);
- }
- gch.Free();
if (imagePtr != IntPtr.Zero)
{
@@ -319,10 +319,12 @@ namespace ReFuel.Stb
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
- wrapper.CreateCallbacks(out stbi_io_callbacks cb);
-
+ int result;
stream.Position = 0;
- int result = Stbi.info_from_callbacks(&cb, null, &x, &y, &iFormat);
+ fixed (stbi_io_callbacks* cb = &wrapper.Callbacks)
+ {
+ result = Stbi.info_from_callbacks(cb, null, &x, &y, &iFormat);
+ }
width = x;
height = y;
diff --git a/StbiStreamWrapper.cs b/StbiStreamWrapper.cs
index 37b6ad1..482ab93 100644
--- a/StbiStreamWrapper.cs
+++ b/StbiStreamWrapper.cs
@@ -32,11 +32,12 @@ namespace ReFuel.Stb
public unsafe delegate int StbiEofProc(void *userdata);
///
- /// An easy to use stream wrapper for use with STBI image load functions.
+ /// An easy-to-use stream wrapper for use with STBI image load functions.
///
public unsafe class StbiStreamWrapper : IDisposable
{
- private readonly stbi_io_callbacks _callbacks;
+ public readonly stbi_io_callbacks Callbacks;
+
private readonly Stream _stream;
private readonly bool _keepOpen;
private bool _isDisposed;
@@ -45,8 +46,6 @@ namespace ReFuel.Stb
private StbiSkipProc _skipCb;
private StbiEofProc _eofCb;
- public ref readonly stbi_io_callbacks Callbacks => ref _callbacks;
-
public StbiStreamWrapper(Stream stream, bool keepOpen = false)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
@@ -58,18 +57,10 @@ namespace ReFuel.Stb
_skipCb = SkipCb;
_eofCb = EofCb;
- _callbacks = default;
- _callbacks.read = Marshal.GetFunctionPointerForDelegate(_readCb);
- _callbacks.skip = Marshal.GetFunctionPointerForDelegate(_skipCb);
- _callbacks.eof = Marshal.GetFunctionPointerForDelegate(_eofCb);
- }
-
- public void CreateCallbacks(out stbi_io_callbacks cb)
- {
- cb = default;
- cb.read = Marshal.GetFunctionPointerForDelegate(_readCb);
- cb.skip = Marshal.GetFunctionPointerForDelegate(_skipCb);
- cb.eof = Marshal.GetFunctionPointerForDelegate(_eofCb);
+ Callbacks = default;
+ Callbacks.read = Marshal.GetFunctionPointerForDelegate(_readCb);
+ Callbacks.skip = Marshal.GetFunctionPointerForDelegate(_skipCb);
+ Callbacks.eof = Marshal.GetFunctionPointerForDelegate(_eofCb);
}
private int ReadCb(void *userdata, byte* buffer, int count)