Get rid of DisposableGCHandle.

Use Span functionality instead. This introduces some copies for .NET Standard 2.0 builds.
This commit is contained in:
Ray Koopa 2020-06-26 18:57:01 +02:00
parent b4cf309156
commit 57815e9072
5 changed files with 239 additions and 271 deletions

View File

@ -1,44 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Core
{
/// <summary>
/// Represents a disposable <see cref="GCHandle"/>.
/// </summary>
internal class DisposableGCHandle : IDisposable
{
// ---- FIELDS -------------------------------------------------------------------------------------------------
private readonly GCHandle _handle;
private bool _disposed;
// ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------
public DisposableGCHandle(object value, GCHandleType type) => _handle = GCHandle.Alloc(value, type);
// ---- PROPERTIES ---------------------------------------------------------------------------------------------
public IntPtr AddrOfPinnedObject => _handle.AddrOfPinnedObject();
// ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
public void Dispose() => Dispose(true);
// ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
if (_handle.IsAllocated)
_handle.Free();
}
_disposed = true;
}
}
}

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Core.Graphics namespace Syroot.Worms.Core.Graphics
{ {
@ -20,16 +20,15 @@ namespace Syroot.Worms.Core.Graphics
/// <param name="palette">The palette as a <see cref="Color"/> array.</param> /// <param name="palette">The palette as a <see cref="Color"/> array.</param>
/// <param name="data">The data array storing bytes indexing the palette color array.</param> /// <param name="data">The data array storing bytes indexing the palette color array.</param>
/// <returns>The <see cref="Bitmap"/> instance.</returns> /// <returns>The <see cref="Bitmap"/> instance.</returns>
public static Bitmap CreateIndexed(Size size, IList<Color> palette, byte[] data) public static unsafe Bitmap CreateIndexed(Size size, IList<Color> palette, byte[] data)
{ {
using DisposableGCHandle dataPin = new DisposableGCHandle(data, GCHandleType.Pinned);
// Transfer the pixel data, respecting power-of-2 strides. // Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format8bppIndexed); Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int y = 0; y < size.Height; y++) Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
Marshal.Copy(data, y * size.Width, bitmapData.Scan0 + y * bitmapData.Stride, size.Width); for (int y = 0; y < size.Height; y++)
data.AsSpan(y * size.Width, size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
bitmap.UnlockBits(bitmapData); bitmap.UnlockBits(bitmapData);
// Transfer the palette. // Transfer the palette.

View File

@ -1,8 +1,7 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Syroot.Worms.Core;
namespace Syroot.Worms namespace Syroot.Worms
{ {
@ -39,16 +38,15 @@ namespace Syroot.Worms
/// Creates a <see cref="Bitmap"/> from the raw data. /// Creates a <see cref="Bitmap"/> from the raw data.
/// </summary> /// </summary>
/// <returns>The <see cref="Bitmap"/> created from the raw data.</returns> /// <returns>The <see cref="Bitmap"/> created from the raw data.</returns>
public Bitmap ToBitmap() public unsafe Bitmap ToBitmap()
{ {
using DisposableGCHandle dataPin = new DisposableGCHandle(Data, GCHandleType.Pinned);
// Transfer the pixel data, respecting power-of-2 strides. // Transfer the pixel data, respecting power-of-2 strides.
Bitmap bitmap = new Bitmap(Size.Width, Size.Height, PixelFormat.Format8bppIndexed); Bitmap bitmap = new Bitmap(Size.Width, Size.Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height), BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Size.Width, Size.Height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int y = 0; y < Size.Height; y++) Span<byte> bitmapSpan = new Span<byte>(bitmapData.Scan0.ToPointer(), bitmapData.Stride * bitmapData.Height);
Marshal.Copy(Data, y * Size.Width, bitmapData.Scan0 + y * bitmapData.Stride, Size.Width); for (int y = 0; y < Size.Height; y++)
Data.AsSpan(y * Size.Width, Size.Width).CopyTo(bitmapSpan.Slice(y * bitmapData.Stride));
bitmap.UnlockBits(bitmapData); bitmap.UnlockBits(bitmapData);
// Transfer the palette. // Transfer the palette.

View File

@ -1,211 +1,225 @@
using System.Collections.Generic; using System;
using System.Diagnostics; using System.Collections.Generic;
using System.IO; using System.Diagnostics;
using System.Runtime.InteropServices; using System.IO;
using Syroot.BinaryData; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Syroot.Worms.Core.IO using Syroot.BinaryData;
{
/// <summary> namespace Syroot.Worms.Core.IO
/// Represents extension methods for <see cref="BinaryStream"/> instances. {
/// </summary> /// <summary>
[DebuggerStepThrough] /// Represents extension methods for <see cref="BinaryStream"/> instances.
public static partial class BinaryStreamExtensions /// </summary>
{ [DebuggerStepThrough]
// ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public static partial class BinaryStreamExtensions
{
// ---- Reading ---- // ---- METHODS (PUBLIC) ---------------------------------------------------------------------------------------
/// <summary> // ---- Reading ----
/// Reads an <see cref="ILoadable"/> instance from the current stream.
/// </summary> /// <summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam> /// Reads an <see cref="ILoadable"/> instance from the current stream.
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// </summary>
/// <returns>The <see cref="ILoadable"/> instance.</returns> /// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
public static T Load<T>(this BinaryStream self) where T : ILoadable, new() /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
{ /// <returns>The <see cref="ILoadable"/> instance.</returns>
T instance = new T(); public static T Load<T>(this BinaryStream self) where T : ILoadable, new()
instance.Load(self.BaseStream); {
return instance; T instance = new T();
} instance.Load(self.BaseStream);
return instance;
/// <summary> }
/// Reads <see cref="ILoadable"/> instances from the current stream.
/// </summary> /// <summary>
/// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam> /// Reads <see cref="ILoadable"/> instances from the current stream.
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// </summary>
/// <param name="count">The number of instances to read.</param> /// <typeparam name="T">The type of the <see cref="ILoadable"/> class to instantiate.</typeparam>
/// <returns>The <see cref="ILoadable"/> instances.</returns> /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
public static T[] Load<T>(this BinaryStream self, int count) where T : ILoadable, new() /// <param name="count">The number of instances to read.</param>
{ /// <returns>The <see cref="ILoadable"/> instances.</returns>
T[] instances = new T[count]; public static T[] Load<T>(this BinaryStream self, int count) where T : ILoadable, new()
for (int i = 0; i < count; i++) {
instances[i] = Load<T>(self); T[] instances = new T[count];
return instances; for (int i = 0; i < count; i++)
} instances[i] = Load<T>(self);
return instances;
/// <summary> }
/// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// </summary> /// <summary>
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// Reads a 0-terminated string which is stored in a fixed-size block of <paramref name="length"/> bytes.
/// <param name="length">The number of bytes the fixed-size blocks takes.</param> /// </summary>
/// <returns>The read string.</returns> /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
public static string ReadString(this BinaryStream self, int length) /// <param name="length">The number of bytes the fixed-size blocks takes.</param>
{ /// <returns>The read string.</returns>
// TODO: This may not work with multi-byte encodings. public static string ReadString(this BinaryStream self, int length)
// Ensure to not try to decode any bytes after the 0 termination. {
byte[] bytes = self.ReadBytes(length); // TODO: This may not work with multi-byte encodings.
while (bytes[--length] == 0 && length > 0) { } // Ensure to not try to decode any bytes after the 0 termination.
return self.Encoding.GetString(bytes, 0, length + 1); byte[] bytes = self.ReadBytes(length);
} while (bytes[--length] == 0 && length > 0) { }
return self.Encoding.GetString(bytes, 0, length + 1);
/// <summary> }
/// Reads <paramref name="count"/> 0-terminated strings which are stored in a fixed-size block of
/// <paramref name="length"/> bytes. /// <summary>
/// </summary> /// Reads <paramref name="count"/> 0-terminated strings which are stored in a fixed-size block of
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// <paramref name="length"/> bytes.
/// <param name="count">The number of values to read.</param> /// </summary>
/// <param name="length">The number of bytes the fixed-size blocks takes.</param> /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
/// <returns>The read string.</returns> /// <param name="count">The number of values to read.</param>
public static string[] ReadStrings(this BinaryStream self, int count, int length) /// <param name="length">The number of bytes the fixed-size blocks takes.</param>
{ /// <returns>The read string.</returns>
string[] strings = new string[count]; public static string[] ReadStrings(this BinaryStream self, int count, int length)
for (int i = 0; i < count; i++) {
strings[i] = ReadString(self, length); string[] strings = new string[count];
return strings; for (int i = 0; i < count; i++)
} strings[i] = ReadString(self, length);
return strings;
/// <summary> }
/// Reads a raw byte structure from the current stream and returns it.
/// </summary> /// <summary>
/// <typeparam name="T">The type of the structure to read.</typeparam> /// Reads a raw byte structure from the current stream and returns it.
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> /// </summary>
/// <returns>The structure of type <typeparamref name="T"/>.</returns> /// <typeparam name="T">The type of the structure to read.</typeparam>
public static T ReadStruct<T>(this BinaryStream self) where T : struct /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
{ /// <returns>The structure of type <typeparamref name="T"/>.</returns>
// Read the raw bytes of the structure. public static T ReadStruct<T>(this BinaryStream self) where T : struct
byte[] bytes = self.ReadBytes(Marshal.SizeOf<T>()); {
// Read the raw bytes of the structure.
// Convert them to a structure instance and return it. #if NETSTANDARD2_0
using DisposableGCHandle handle = new DisposableGCHandle(bytes, GCHandleType.Pinned); byte[] bytes = new byte[Unsafe.SizeOf<T>()];
T instance = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject); self.Read(bytes, 0, bytes.Length);
return instance; #else
} Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>()];
self.Read(bytes);
/// <summary> #endif
/// Reads raw byte structures from the current stream and returns them.
/// </summary> // Convert them to a structure instance and return it.
/// <typeparam name="T">The type of the structure to read.</typeparam> ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> return span[0];
/// <param name="count">The number of values to read.</param> }
/// <returns>The structures of type <typeparamref name="T"/>.</returns>
public static T[] ReadStructs<T>(this BinaryStream self, int count) where T : struct /// <summary>
{ /// Reads raw byte structures from the current stream and returns them.
T[] values = new T[count]; /// </summary>
for (int i = 0; i < count; i++) /// <typeparam name="T">The type of the structure to read.</typeparam>
values[i] = ReadStruct<T>(self); /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
return values; /// <param name="count">The number of values to read.</param>
} /// <returns>The structures of type <typeparamref name="T"/>.</returns>
public static T[] ReadStructs<T>(this BinaryStream self, int count) where T : struct
/// <summary> {
/// Returns the current address of the stream to which a 4-byte placeholder has been written which can be filled // Read the raw bytes of the structures.
/// later. #if NETSTANDARD2_0
/// </summary> byte[] bytes = new byte[Unsafe.SizeOf<T>()];
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> self.Read(bytes, 0, bytes.Length);
/// <returns>The address at which a 4-byte placeholder has been written to.</returns> #else
public static uint ReserveOffset(this BinaryStream self) Span<byte> bytes = stackalloc byte[Unsafe.SizeOf<T>() * count];
{ self.Read(bytes);
uint offset = (uint)self.Position; #endif
self.WriteUInt32(0);
return offset; // Convert them to a structure array and return it.
} ReadOnlySpan<T> span = MemoryMarshal.Cast<byte, T>(bytes);
return span.ToArray();
// ---- Writing ---- }
/// <summary> /// <summary>
/// Writes the given <see cref="ISaveable"/> instance into the current stream. /// Returns the current address of the stream to which a 4-byte placeholder has been written which can be filled
/// </summary> /// later.
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam> /// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="value">The instance to write into the current stream.</param> /// <returns>The address at which a 4-byte placeholder has been written to.</returns>
public static void Save<T>(this BinaryStream self, T value) where T : ISaveable public static uint ReserveOffset(this BinaryStream self)
{ {
value.Save(self.BaseStream); uint offset = (uint)self.Position;
} self.WriteUInt32(0);
return offset;
/// <summary> }
/// Writes the given <see cref="ISaveable"/> instances into the current stream.
/// </summary> // ---- Writing ----
/// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/>.</param> /// <summary>
/// <param name="values">The instances to write into the current stream.</param> /// Writes the given <see cref="ISaveable"/> instance into the current stream.
public static void Save<T>(this BinaryStream self, IList<T> values) where T : ISaveable /// </summary>
{ /// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
foreach (T value in values) /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
Save(self, value); /// <param name="value">The instance to write into the current stream.</param>
} public static void Save<T>(this BinaryStream self, T value) where T : ISaveable
{
public static void SatisfyOffset(this BinaryStream self, uint offset, int value) value.Save(self.BaseStream);
{ }
using (self.TemporarySeek(offset, SeekOrigin.Begin))
self.WriteInt32(value); /// <summary>
} /// Writes the given <see cref="ISaveable"/> instances into the current stream.
/// </summary>
/// <summary> /// <typeparam name="T">The type of the <see cref="ISaveable"/> class to write.</typeparam>
/// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it. /// <param name="self">The extended <see cref="BinaryStream"/>.</param>
/// </summary> /// <param name="values">The instances to write into the current stream.</param>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> public static void Save<T>(this BinaryStream self, IList<T> values) where T : ISaveable
/// <param name="value">The string to write.</param> {
/// <param name="length">The number of bytes the fixed-size block takes.</param> foreach (T value in values)
public static void WriteString(this BinaryStream self, string value, int length) Save(self, value);
{ }
// TODO: This may not work with multi-byte encodings.
byte[] bytes = new byte[length]; public static void SatisfyOffset(this BinaryStream self, uint offset, int value)
if (value != null) {
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0); using (self.TemporarySeek(offset, SeekOrigin.Begin))
self.WriteBytes(bytes); self.WriteInt32(value);
} }
/// <summary> /// <summary>
/// Writes the given strings into fixed-size blocks of <paramref name="length"/> bytes and 0-terminates them. /// Writes the given string into a fixed-size block of <paramref name="length"/> bytes and 0-terminates it.
/// </summary> /// </summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The strings to write.</param> /// <param name="value">The string to write.</param>
/// <param name="length">The number of bytes a fixed-size block takes.</param> /// <param name="length">The number of bytes the fixed-size block takes.</param>
public static void WriteStrings(this BinaryStream self, IList<string> values, int length) public static void WriteString(this BinaryStream self, string value, int length)
{ {
foreach (string value in values) // TODO: This may not work with multi-byte encodings.
WriteString(self, value, length); byte[] bytes = new byte[length];
} if (value != null)
self.Encoding.GetBytes(value, 0, value.Length, bytes, 0);
/// <summary> self.WriteBytes(bytes);
/// Writes the bytes of a structure into the current stream. }
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam> /// <summary>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> /// Writes the given strings into fixed-size blocks of <paramref name="length"/> bytes and 0-terminates them.
/// <param name="value">The structure to write.</param> /// </summary>
public static void WriteStruct<T>(this BinaryStream self, T value) where T : struct /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
{ /// <param name="values">The strings to write.</param>
// Get the raw bytes of the structure instance. /// <param name="length">The number of bytes a fixed-size block takes.</param>
byte[] bytes = new byte[Marshal.SizeOf<T>()]; public static void WriteStrings(this BinaryStream self, IList<string> values, int length)
{
using (DisposableGCHandle handle = new DisposableGCHandle(bytes, GCHandleType.Pinned)) foreach (string value in values)
Marshal.StructureToPtr(value, handle.AddrOfPinnedObject, false); WriteString(self, value, length);
}
// Write the bytes to the stream.
self.WriteStructs(bytes); /// <summary>
} /// Writes the bytes of a structure into the current stream.
/// </summary>
/// <summary> /// <typeparam name="T">The type of the structure to read.</typeparam>
/// Writes the bytes of structures into the current stream. /// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// </summary> /// <param name="value">The structure to write.</param>
/// <typeparam name="T">The type of the structure to read.</typeparam> public static void WriteStruct<T>(this BinaryStream self, T value) where T : struct
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param> {
/// <param name="values">The structures to write.</param> ReadOnlySpan<byte> bytes;
public static void WriteStructs<T>(this BinaryStream self, IList<T> values) where T : struct unsafe { bytes = new ReadOnlySpan<byte>(Unsafe.AsPointer(ref value), Unsafe.SizeOf<T>()); }
{ #if NETSTANDARD2_0
foreach (T value in values) self.Write(bytes.ToArray()); // cannot prevent copy in .NET Standard 2.0
WriteStruct(self, value); #else
} self.Write(bytes);
} #endif
} }
/// <summary>
/// Writes the bytes of structures into the current stream.
/// </summary>
/// <typeparam name="T">The type of the structure to read.</typeparam>
/// <param name="self">The extended <see cref="BinaryStream"/> instance.</param>
/// <param name="values">The structures to write.</param>
public static void WriteStructs<T>(this BinaryStream self, IEnumerable<T> values) where T : struct
{
foreach (T value in values)
self.WriteStruct(value);
}
}
}

View File

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(SolutionDir)build.xml" /> <Import Project="$(SolutionDir)build.xml" />
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Syroot.Worms</AssemblyName> <AssemblyName>Syroot.Worms</AssemblyName>
<Description>.NET library for loading and modifying files of Team17 Worms games.</Description> <Description>.NET library for loading and modifying files of Team17 Worms games.</Description>
<PackageReleaseNotes>Split into game-related packages.</PackageReleaseNotes> <PackageReleaseNotes>Split into game-related packages.</PackageReleaseNotes>