diff --git a/BInaryKit-Test/BInaryKit-Test.csproj b/BInaryKit-Test/BInaryKit-Test.csproj index 916fa9a..4b695a0 100644 --- a/BInaryKit-Test/BInaryKit-Test.csproj +++ b/BInaryKit-Test/BInaryKit-Test.csproj @@ -1,13 +1,14 @@  - net10.0 BinaryKit_Test enable enable false true BinaryKit-Test + 14 + net10.0 @@ -22,7 +23,7 @@ - + \ No newline at end of file diff --git a/BInaryKit-Test/EndianTest.cs b/BInaryKit-Test/EndianTest.cs index f9815e9..b31082e 100644 --- a/BInaryKit-Test/EndianTest.cs +++ b/BInaryKit-Test/EndianTest.cs @@ -10,14 +10,14 @@ public class EndianTest(ITestOutputHelper output) public void Read_int() { const int value = 1000; - var expected = BitConverter.IsLittleEndian ? value : System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value); + var expected = BitConverter.IsLittleEndian ? value : BinaryPrimitives.ReverseEndianness(value); var actual = value; actual = EndianToolkit.Convert(actual, Endianness.Local, Endianness.Little); output.WriteLine("expected: " + expected); output.WriteLine("actual: " + actual); Assert.Equal(expected, actual); } - + [Fact] public unsafe void Read_Struct() { @@ -25,10 +25,7 @@ public unsafe void Read_Struct() var expected = value; var actual = value; // to big endian - if (BitConverter.IsLittleEndian) - { - new Span(&expected, sizeof(UnionStruct)).Reverse(); - } + if (BitConverter.IsLittleEndian) new Span(&expected, sizeof(UnionStruct)).Reverse(); // to big endian actual = EndianToolkit.Convert(actual, Endianness.Local, Endianness.Big); output.WriteLine("expected: " + expected); @@ -41,7 +38,7 @@ public unsafe void Read_Struct() [Fact] public void ReverseManyTest() { - Span old = [10,20,30,40,50,60,70,80]; + Span old = [10, 20, 30, 40, 50, 60, 70, 80]; Span expected = stackalloc int[old.Length]; for (var index = 0; index < old.Length; index++) { @@ -50,15 +47,10 @@ public void ReverseManyTest() } EndianToolkit.ReverseMany(old); - for (int i = 0; i < old.Length; i++) - { + for (var i = 0; i < old.Length; i++) if (old[i] != expected[i]) - { Assert.Fail(); - } - } } - #endregion } \ No newline at end of file diff --git a/BInaryKit-Test/StreamExTest.cs b/BInaryKit-Test/StreamExTest.cs index 518dbb9..8fe44ab 100644 --- a/BInaryKit-Test/StreamExTest.cs +++ b/BInaryKit-Test/StreamExTest.cs @@ -7,6 +7,24 @@ namespace BinaryKit_Test; public class StreamExTest(ITestOutputHelper output) { + #region WriteFrom Tests + + [Fact] + public unsafe void Write() + { + UnionStruct expected = new(); + UnionStruct value = default; + using var stream = new MemoryStream(); + stream.WriteFrom(expected); + stream.Position = 0; + stream.GetBuffer().AsSpan(0, sizeof(UnionStruct)).CopyTo(new Span(&expected, sizeof(UnionStruct))); + output.WriteLine("left: " + expected); + output.WriteLine("right: " + value); + Assert.Equal(expected, value); + } + + #endregion + #region ReadAs Tests [Fact] @@ -65,29 +83,12 @@ public unsafe void Read_UnionStruct() #endregion - #region WriteFrom Tests - - [Fact] - public unsafe void Write() - { - UnionStruct expected = new(); - UnionStruct value = default; - using var stream = new MemoryStream(); - stream.WriteFrom(expected); - stream.Position = 0; - stream.GetBuffer().AsSpan(0, sizeof(UnionStruct)).CopyTo(new Span(&expected, sizeof(UnionStruct))); - output.WriteLine("left: " + expected); - output.WriteLine("right: " + value); - Assert.Equal(expected, value); - } - #endregion - #region Multiple-Test [Fact] public unsafe void MultipleRead() { - byte[] ints = [10,20,30,40]; + byte[] ints = [10, 20, 30, 40]; Span span = stackalloc int[1]; var ptr = (int*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(ints)); var expected = *ptr; @@ -99,17 +100,16 @@ public unsafe void MultipleRead() stream.ReadManyAs(span2); if (span2[0] != *(short*)ptr) Assert.Fail(); if (span2[1] != ((short*)ptr)[1]) Assert.Fail(); - } - + } + [Fact] public void MultipleWrite() { - byte[] ints = [10,20,30,40]; + byte[] ints = [10, 20, 30, 40]; using var stream = new MemoryStream(); stream.WriteManyFrom(ints); - if (stream.GetBuffer() is not [10,20,30,40,..]) Assert.Fail(); - } - + if (stream.GetBuffer() is not [10, 20, 30, 40, ..]) Assert.Fail(); + } #endregion } \ No newline at end of file diff --git a/BInaryKit-Test/UnsafeToolkit/Pin-Test.cs b/BInaryKit-Test/UnsafeToolkit/Pin-Test.cs new file mode 100644 index 0000000..797689b --- /dev/null +++ b/BInaryKit-Test/UnsafeToolkit/Pin-Test.cs @@ -0,0 +1,69 @@ +using Iks.BinaryToolkit.UnsafeToolkit; + +namespace BinaryKit_Test.UnsafeToolkit; + +public class PinTest +{ + // pin must be not null + [Fact] + public void NotNullPin() + { + try + { + // set a null ref + string str = null!; + using var pin = str.AsPinned(); + } + // throws is right,null cannot be pinned + catch + { + return; + } + + Assert.Fail(); + } + + [Fact] + public void NotUseAfterRelease() + { + var pin = new object().AsPinned(); + pin.Dispose(); + // use after release + try + { + var pinTarget = pin.Target; + } + catch + { + return; + } + + Assert.Fail(); + } + + [Fact] + public void NotMultipleRelease() + { + var pin = new object().AsPinned(); + pin.Dispose(); + // release again + try + { + pin.Dispose(); + } + catch + { + return; + } + + Assert.Fail(); + } + + [Fact] + public void CopyRelease() + { + var pin_old = new object().AsPinned(); + var pin_new = pin_old; + pin_new.Dispose(); + } +} \ No newline at end of file diff --git a/BinaryToolKit.slnx b/BinaryToolKit.slnx index 7d9d028..988c9e0 100644 --- a/BinaryToolKit.slnx +++ b/BinaryToolKit.slnx @@ -1,17 +1,17 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/BinaryToolKit/BinaryStreamToolkit.cs b/BinaryToolKit/BinaryStreamToolkit.cs index 939d89e..d402c66 100644 --- a/BinaryToolKit/BinaryStreamToolkit.cs +++ b/BinaryToolKit/BinaryStreamToolkit.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Iks.BinaryToolKit.Message; namespace Iks.BinaryToolkit; @@ -62,7 +63,7 @@ allows ref struct } /// - /// write an unmanaged type(value) to the stream. + /// write an unmanaged type(value) to the stream. /// /// target type [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -75,9 +76,9 @@ allows ref struct { stream.Write(new ReadOnlySpan(&value, sizeof(T))); } - + /// - /// write an unmanaged type(value) to the stream. + /// write an unmanaged type(value) to the stream. /// /// target type [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -106,7 +107,7 @@ allows ref struct [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadManyAs(Span targetSpan) where T : unmanaged { - fixed(T* ptrT = &MemoryMarshal.GetReference(targetSpan)) + fixed (T* ptrT = &MemoryMarshal.GetReference(targetSpan)) { ReadManyAs(stream, ptrT, targetSpan.Length); } @@ -154,9 +155,9 @@ allows ref struct /// target position public unsafe void WriteManyFrom(ReadOnlySpan values) where T : unmanaged { - fixed(T* ptrT = &MemoryMarshal.GetReference(values)) + fixed (T* ptrT = &MemoryMarshal.GetReference(values)) { - WriteManyFrom(stream,ptrT, values.Length); + WriteManyFrom(stream, ptrT, values.Length); } } diff --git a/BinaryToolKit/BinaryToolKit.csproj b/BinaryToolKit/BinaryToolKit.csproj index bfc99f9..6f7ff86 100644 --- a/BinaryToolKit/BinaryToolKit.csproj +++ b/BinaryToolKit/BinaryToolKit.csproj @@ -27,7 +27,7 @@ TRACE RELEASE true true - bin\PackNuget\BinaryToolKit.xml + true portable true snupkg diff --git a/BinaryToolKit/EndianToolkit.cs b/BinaryToolKit/EndianToolkit.cs index fd0c302..0d71e00 100644 --- a/BinaryToolKit/EndianToolkit.cs +++ b/BinaryToolKit/EndianToolkit.cs @@ -5,7 +5,7 @@ namespace Iks.BinaryToolkit; /// -/// provides methods to process endianness of unmanaged types,including int,float. +/// provides methods to process endianness of unmanaged types,including int,float. /// public static class EndianToolkit { @@ -43,7 +43,7 @@ private static unsafe void ReverseNoCheck(void* value, int length) #region Single /// - /// reverses the endianness of an unmanaged type(value). + /// reverses the endianness of an unmanaged type(value). /// /// value wait to reverse /// type you want to reverse @@ -60,7 +60,7 @@ allows ref struct } /// - /// reverses the endianness of an unmanaged type(value). + /// reverses the endianness of an unmanaged type(value). /// /// target position you want to reverse /// The size of the element that this pointer points to @@ -72,8 +72,9 @@ public static unsafe void Reverse(void* value, int length) /// - /// converts the endianness of an unmanaged type(value) from one to another. - /// it cannot to make sure struct members are all in the same endian.it just reverses byte-endianness of the whole struct. + /// converts the endianness of an unmanaged type(value) from one to another. + /// it cannot to make sure struct members are all in the same endian.it just reverses byte-endianness of the whole + /// struct. /// /// target value /// source endian,can use local @@ -87,10 +88,7 @@ allows ref struct #endif { // differ, reverse - if (from != to) - { - ReverseNoCheck(&value, sizeof(T)); - } + if (from != to) ReverseNoCheck(&value, sizeof(T)); return value; } @@ -101,7 +99,7 @@ allows ref struct #region Multiple /// - /// reverses the endianness of multiple unmanaged type(values). + /// reverses the endianness of multiple unmanaged type(values). /// /// target enumerable wait to reverse [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -114,7 +112,7 @@ public static unsafe void ReverseMany(Span values) where T : unmanaged } /// - /// reverses the endianness of multiple unmanaged type(values). + /// reverses the endianness of multiple unmanaged type(values). /// /// target position to reverse endianness /// The size of the element that this pointer points to @@ -133,7 +131,7 @@ public static unsafe void ReverseMany(void* target, int elementSize, int element } /// - /// converts the endianness of multiple unmanaged type(value) from one to another. + /// converts the endianness of multiple unmanaged type(value) from one to another. /// /// target position to reverse endianness /// source endian @@ -148,7 +146,7 @@ public static unsafe void ConvertMany(Span target, Endianness from, Endian } /// - /// converts the endianness of multiple unmanaged type(value) from one to another. + /// converts the endianness of multiple unmanaged type(value) from one to another. /// /// target position to reverse endianness /// The size of the element that this pointer points to @@ -169,32 +167,39 @@ public static unsafe void ConvertMany(void* target, int elementSize, int element } /// -/// endianness type definition +/// endianness type definition /// public enum Endianness { /// - /// little-endian byte order + /// little-endian byte order /// Little, /// - /// big-endian byte order + /// big-endian byte order /// Big } -/// Extension of ,provide Local +/// Extension of +/// +/// ,provide +/// Local public static class EndiannessExtension { - private static readonly Endianness LocalEndianness = + /// + /// Same as Local,Just to support C# 13 and less + /// + public static readonly Endianness LocalEndianness = BitConverter.IsLittleEndian ? Endianness.Little : Endianness.Big; + /// extension(Endianness obj) { /// - /// local machine byte order - /// same as BitConverter.IsLittleEndian + /// local machine byte order + /// same as BitConverter.IsLittleEndian /// public static Endianness Local => LocalEndianness; } diff --git a/BinaryToolKit/ErrorMessage.cs b/BinaryToolKit/Message/ErrorMessage.cs similarity index 86% rename from BinaryToolKit/ErrorMessage.cs rename to BinaryToolKit/Message/ErrorMessage.cs index ae16abe..bc0ece0 100644 --- a/BinaryToolKit/ErrorMessage.cs +++ b/BinaryToolKit/Message/ErrorMessage.cs @@ -1,4 +1,4 @@ -namespace Iks.BinaryToolkit; +namespace Iks.BinaryToolKit.Message; internal static class ErrorMessage { diff --git a/BinaryToolKit/Message/ObsoleteMessage.cs b/BinaryToolKit/Message/ObsoleteMessage.cs new file mode 100644 index 0000000..caaf0a0 --- /dev/null +++ b/BinaryToolKit/Message/ObsoleteMessage.cs @@ -0,0 +1,7 @@ +namespace Iks.BinaryToolKit.Message; + +internal static class ObsoleteMessage +{ + public const string Use_PinnedGCHandle_From_Standard = + "This Wrapper has already been obsoleted.Use standard Type PinnedGCHandle from ` System.Runtime.InteropServices.PinnedGCHandle `"; +} \ No newline at end of file diff --git a/BinaryToolKit/UnsafeToolkit/Pin.cs b/BinaryToolKit/UnsafeToolkit/Pin.cs new file mode 100644 index 0000000..1c54ddc --- /dev/null +++ b/BinaryToolKit/UnsafeToolkit/Pin.cs @@ -0,0 +1,139 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Iks.BinaryToolKit.Message; + +namespace Iks.BinaryToolkit.UnsafeToolkit; + +/// +/// Generics Supported and Auto-Release of . +/// +/// An object which you want to pin for sometime +#if NET10_0_OR_GREATER +[Obsolete(ObsoleteMessage.Use_PinnedGCHandle_From_Standard, false)] +#endif +[DebuggerDisplay("Address = {Address}, Target = {Target}")] +public struct Pin(TPinned objectNeededPinning) : IDisposable, IEquatable> where TPinned : class +{ + private GCHandle _handle = + GCHandle.Alloc(objectNeededPinning ?? throw new ArgumentNullException(nameof(objectNeededPinning)), + GCHandleType.Pinned); + + /// + /// Get pinned object + /// + public readonly TPinned Target => (TPinned)_handle.Target!; + + /// + /// Retrieves the address of object data in a Pinned handle. + /// + /// the address of the object + public readonly IntPtr Address => _handle.AddrOfPinnedObject(); + + /// Free the to unpin it + public void Dispose() + { + _handle.Free(); + } + + #region Wrapper GCHandle + + /// + /// Equals As + /// Returns an identifier for the current object. + /// + /// An identifier for the current object + public readonly override int GetHashCode() + { + return _handle.GetHashCode(); + } + + /// + /// Same as GCHandle.Equals. + /// Indicates whether the current instance is equal to another instance of the same type. + /// + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Pin other) + { + return _handle.Equals(other._handle); + } + + /// + /// Same as GCHandle.Equals. + /// Indicates whether the current instance is equal to another instance of the same type. + /// + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(GCHandle other) + { + return _handle.Equals(other); + } + + /// + /// Same as GCHandle.Equals. + /// Indicates whether the current instance is equal to another instance of the same type. + /// + /// An instance to compare with this instance. + /// true if the current instance is equal to the other instance; otherwise, false. + public readonly override bool Equals(object? other) + { + return other is GCHandle obj && _handle.Equals(obj); + } + + /// + /// Same As + /// + /// Equals + /// + /// + public static bool operator ==(Pin left, Pin right) + { + return left.Equals(right); + } + + /// + /// Same As + /// + /// Equals + /// + /// + public static bool operator !=(Pin left, Pin right) + { + return !(left == right); + } + + /// + /// Display Pin ,including Address and Target.ToString() + /// + public override string ToString() + { + return $"Pinned Address = {Address}, Target = {Target}"; + } + + #endregion +} + +/// +/// Provide Pin extension ,such as PinIt, +/// +#if NET10_0_OR_GREATER +[Obsolete(ObsoleteMessage.Use_PinnedGCHandle_From_Standard, false)] +#endif +public static class PinExtension +{ + /// + extension(TPinned obj) where TPinned : class + { + /// + /// Make a Pin<TPinned> from obj + /// + /// + public Pin AsPinned() + { + return new Pin(obj); + } + } +} \ No newline at end of file diff --git a/Feature.md b/Feature.md index 21c3306..26ddebc 100644 --- a/Feature.md +++ b/Feature.md @@ -1,33 +1,49 @@ # Finished-Feature + + ### `BinaryStreamToolkit` - + 用于以泛型的方式读写Stream中的数据 - + 主要行为: Ex:Stream - + 命名空间: Iks.BinaryToolkit.BinaryStreamToolkit - + 源码位置: 自己翻 - + `ReadAs`(out与返回值模式两种出参方式)、`WriteFrom` + + 用于以泛型的方式读写Stream中的数据 + + 主要行为: Ex:Stream + + 命名空间: Iks.BinaryToolkit.BinaryStreamToolkit + + 源码位置: 自己翻 + + `ReadAs`(out与返回值模式两种出参方式)、`WriteFrom` + ### `Multiple-BinaryStreamToolkit` - + 用于以泛型的方式读取多个数据,提供`指针(T*)`与`Span`支持 - + 主要行为: Ex:Stream - + 命名空间: Iks.BinaryToolkit.BinaryStreamToolkit - + 源码位置: 自己翻 - + `ReadManyAs`、`WriteManyFrom` + + 用于以泛型的方式读取多个数据,提供`指针(T*)`与`Span`支持 + + 主要行为: Ex:Stream + + 命名空间: Iks.BinaryToolkit.BinaryStreamToolkit + + 源码位置: 自己翻 + + `ReadManyAs`、`WriteManyFrom` + ### `EndianToolkit` - + 用于以泛型的方式反转端序,算是`BinaryPrimitives`的增加与封装 - + 主要行为: static: EndianToolkit - + 命名空间: Iks.BinaryToolkit.EndianToolkit - + 源码位置: 自己翻 - + `Convert(value,from,to)`语义明确,支持填写Local: `from Local to Big` - + 能`allows ref struct`的尽力allow了,如果`ref struct`的内存布局里真有`指针(T*,ref)`不行(*`.NET 8因为不能allow也不行`*) - + `Reverse` 同样的`指针(T*)`与`Span`出参 + + 用于以泛型的方式反转端序,算是`BinaryPrimitives`的增加与封装 + + 主要行为: static: EndianToolkit + + 命名空间: Iks.BinaryToolkit.EndianToolkit + + 源码位置: 自己翻 + + `Convert(value,from,to)`语义明确,支持填写Local: `from Local to Big` + + 能`allows ref struct`的尽力allow了,如果`ref struct`的内存布局里真有`指针(T*,ref)`不行( + *`.NET 8因为不能allow也不行`*) + + `Reverse` 同样的`指针(T*)`与`Span`出参 + ### Multiple-EndianToolkit - + 用于以泛型的方式反转集合元素端序,与`Multiple-BinaryStreamToolkit`配合使用 - + 主要行为: static: EndianToolkit - + 命名空间: Iks.BinaryToolkit.EndianToolkit - + 源码位置: 自己翻 - + `ConvertMany(values,from,to)`同上,针对多个元素不会多次评估是否包含`指针(T*,ref)` - + `ReverseMany`依旧是`Span`和`指针(T*)` + + 用于以泛型的方式反转集合元素端序,与`Multiple-BinaryStreamToolkit`配合使用 + + 主要行为: static: EndianToolkit + + 命名空间: Iks.BinaryToolkit.EndianToolkit + + 源码位置: 自己翻 + + `ConvertMany(values,from,to)`同上,针对多个元素不会多次评估是否包含`指针(T*,ref)` + + `ReverseMany`依旧是`Span`和`指针(T*)` ++ ### `Pin` ,A `GCHandle` Wrapper + + 为NET10以下版本提供一个带Dispose方法的GCHandle + + + NET10以上请使用标准库[PinnedGCHandle\](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.pinnedgchandle-1?view=net-10.0) + + 行为与NET10的`PinnedGCHandle`有些不一致,`PinnedGCHandle`类型范围更大,它不检查对象是否是`可Pin`的 + + 我的实现`Pin`作为`GCHandle`的封装,并不能绕过`GCHandle`的API限制 + + 主要行为: struct Pin/static PinExtension + + 命名空间: Iks.BinaryToolkit.UnsafeToolkit.Pin + + 源码位置: 自己翻 + + 用途: 暂时pin住一个对象,用于与非托管代码交互,主要提供语法简洁性、额外的null检查、using自动释放 + +> [!CAUTION] +> Remember To Dispose It! # Thinking + + ## 还没想好(大声) + ### 欢迎贡献 \ No newline at end of file diff --git a/README.md b/README.md index 3750c32..141a22e 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,45 @@ # Feature: + #### [跳转到`Feature`](Feature.md) # Github-Actions + #### 可能我调的`actions`比我的库有用一点( + #### [跳转到我的`Actions`](.github/workflows/dotnet-build-publish.yml) + + 只需要为仓库添加`NUGET_API_KEY机密`并修改`env: project`即可使用 + **通过`打Tag`执行发布流程** - + `版本号` 就是 `Tag`名 - + `Preview版本`在`Tag`中后缀`preview` - + 自动生成符号包`snupkg`并推送 - + 您只需要关心`Icon`,`LICENSE`,`ReadMeFile`和`包描述`即可(都是IDE中很好指定的) + + `版本号` 就是 `Tag`名 + + `Preview版本`在`Tag`中后缀`preview` + + 自动生成符号包`snupkg`并推送 + + 您只需要关心`Icon`,`LICENSE`,`ReadMeFile`和`包描述`即可(都是IDE中很好指定的) + # 为什么我们需要泛型支持? -+ + ++ + `T Stream.ReadAs()` + 用于从Stream中读取一个非托管类型数据(如int,Int128) + `void Stream.Write(in T)` + 用于向Stream中写入一个非托管类型数据(如int,Int128) + ### 我们不需要将函数名当泛型使用的函数,请考虑以下场景 + 如果您需要实现一种二进制格式的数据的读写,如果您使用NET提供的蹩脚`BinaryReader`作为您的辅助 - ```csharp - class DataReader - { - BinaryReader _reader; - public int ReadInt(){ - return _reader.ReadInt(); - } - ... +```csharp +class DataReader +{ + BinaryReader _reader; + public int ReadInt(){ + return _reader.ReadInt(); } - ``` - 您会发现您被BinaryReader绑架了,要么你自己也各种`ReadInt`,`ReadShort`,要么你就得switch进行类型分发,现在,我替您完成了简单的类型读写操作 + ... +} +``` +您会发现您被BinaryReader绑架了,要么你自己也各种`ReadInt`,`ReadShort`,要么你就得switch进行类型分发,现在,我替您完成了简单的类型读写操作 + `Iks.Binary.EndianToolkit`理由同上,且有更严重的类型分发问题 - + 如果您采用了泛型,即使您将泛型参数类型限制到`INumber`,您也用不了`BinaryPrimitives`进行读写,如果您不希望使用非安全代码您可能会写出类似下面的东西 + + 如果您采用了泛型,即使您将泛型参数类型限制到`INumber`,您也用不了`BinaryPrimitives` + 进行读写,如果您不希望使用非安全代码您可能会写出类似下面的东西 ```csharp void Usage(T value) {