diff options
Diffstat (limited to 'src/mscorlib/corefx/System/Runtime/InteropServices')
4 files changed, 717 insertions, 0 deletions
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs new file mode 100644 index 0000000000..875009aee2 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements. + /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero. + /// + /// NativeBuffer utilizes a cache of heap buffers. + /// </summary> + /// <remarks> + /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle. + /// + /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke. + /// (Notably AddRef and ReleaseRef will be called by the interop layer.) + /// + /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap + /// handles or worse. + /// </remarks> + internal class NativeBuffer : IDisposable + { + private readonly static SafeHeapHandleCache s_handleCache = new SafeHeapHandleCache(); + private readonly static SafeHandle s_emptyHandle = new EmptySafeHandle(); + private SafeHeapHandle _handle; + private ulong _capacity; + + /// <summary> + /// Create a buffer with at least the specified initial capacity in bytes. + /// </summary> + public NativeBuffer(ulong initialMinCapacity = 0) + { + EnsureByteCapacity(initialMinCapacity); + } + + protected unsafe void* VoidPointer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _handle == null ? null : _handle.DangerousGetHandle().ToPointer(); + } + } + + protected unsafe byte* BytePointer + { + get + { + return (byte*)VoidPointer; + } + } + + /// <summary> + /// Get the handle for the buffer. + /// </summary> + public SafeHandle GetHandle() + { + // Marshalling code will throw on null for SafeHandle + return _handle ?? s_emptyHandle; + } + + /// <summary> + /// The capacity of the buffer in bytes. + /// </summary> + public ulong ByteCapacity + { + get { return _capacity; } + } + + /// <summary> + /// Ensure capacity in bytes is at least the given minimum. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set <paramref name="nameof(minCapacity)"/> to a value that is larger than the maximum addressable memory.</exception> + public void EnsureByteCapacity(ulong minCapacity) + { + if (_capacity < minCapacity) + { + Resize(minCapacity); + _capacity = minCapacity; + } + } + + public unsafe byte this[ulong index] + { + get + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + return BytePointer[index]; + } + set + { + if (index >= _capacity) throw new ArgumentOutOfRangeException(); + BytePointer[index] = value; + } + } + + private unsafe void Resize(ulong byteLength) + { + if (byteLength == 0) + { + ReleaseHandle(); + return; + } + + if (_handle == null) + { + _handle = s_handleCache.Acquire(byteLength); + } + else + { + _handle.Resize(byteLength); + } + } + + private void ReleaseHandle() + { + if (_handle != null) + { + s_handleCache.Release(_handle); + _capacity = 0; + _handle = null; + } + } + + /// <summary> + /// Release the backing buffer + /// </summary> + public virtual void Free() + { + ReleaseHandle(); + } + + public void Dispose() + { + Free(); + } + + private sealed class EmptySafeHandle : SafeHandle + { + public EmptySafeHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid + { + get { return true; } + } + + protected override bool ReleaseHandle() + { + return true; + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs new file mode 100644 index 0000000000..92b3d980db --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Handle for heap memory that allows tracking of capacity and reallocating. + /// </summary> + internal sealed class SafeHeapHandle : SafeBuffer + { + /// <summary> + /// Allocate a buffer of the given size if requested. + /// </summary> + /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> + /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> + public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true) + { + Resize(byteLength); + } + + public override bool IsInvalid + { + get { return handle == IntPtr.Zero; } + } + + /// <summary> + /// Resize the buffer to the given size if requested. + /// </summary> + /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> + /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> + public void Resize(ulong byteLength) + { + if (IsClosed) throw new ObjectDisposedException(nameof(SafeHeapHandle)); + + ulong originalLength = 0; + if (handle == IntPtr.Zero) + { + handle = Marshal.AllocHGlobal((IntPtr)byteLength); + } + else + { + originalLength = ByteLength; + + // This may or may not be the same handle, may realloc in place. If the + // handle changes Windows will deal with the old handle, trying to free it will + // cause an error. + handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength); + } + + if (handle == IntPtr.Zero) + { + // Only real plausible answer + throw new OutOfMemoryException(); + } + + if (byteLength > originalLength) + { + // Add pressure + ulong addedBytes = byteLength - originalLength; + if (addedBytes > long.MaxValue) + { + GC.AddMemoryPressure(long.MaxValue); + GC.AddMemoryPressure((long)(addedBytes - long.MaxValue)); + } + else + { + GC.AddMemoryPressure((long)addedBytes); + } + } + else + { + // Shrank or did nothing, release pressure if needed + RemoveMemoryPressure(originalLength - byteLength); + } + + Initialize(byteLength); + } + + private void RemoveMemoryPressure(ulong removedBytes) + { + if (removedBytes == 0) return; + + if (removedBytes > long.MaxValue) + { + GC.RemoveMemoryPressure(long.MaxValue); + GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue)); + } + else + { + GC.RemoveMemoryPressure((long)removedBytes); + } + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + { + RemoveMemoryPressure(ByteLength); + Marshal.FreeHGlobal(handle); + } + + handle = IntPtr.Zero; + return true; + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs new file mode 100644 index 0000000000..725076ed66 --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Allows limited thread safe reuse of heap buffers to limit memory pressure. + /// + /// This cache does not ensure that multiple copies of handles are not released back into the cache. + /// </summary> + internal sealed class SafeHeapHandleCache : IDisposable + { + private readonly ulong _minSize; + private readonly ulong _maxSize; + + // internal for testing + internal readonly SafeHeapHandle[] _handleCache; + + /// <param name="minSize">Smallest buffer size to allocate in bytes.</param> + /// <param name="maxSize">The largest buffer size to cache in bytes.</param> + /// <param name="maxHandles">The maximum number of handles to cache.</param> + public SafeHeapHandleCache(ulong minSize = 64, ulong maxSize = 1024 * 2, int maxHandles = 0) + { + _minSize = minSize; + _maxSize = maxSize; + _handleCache = new SafeHeapHandle[maxHandles > 0 ? maxHandles : Environment.ProcessorCount * 4]; + } + + /// <summary> + /// Get a HeapHandle + /// </summary> + public SafeHeapHandle Acquire(ulong minSize = 0) + { + if (minSize < _minSize) minSize = _minSize; + + SafeHeapHandle handle = null; + + for (int i = 0; i < _handleCache.Length; i++) + { + handle = Interlocked.Exchange(ref _handleCache[i], null); + if (handle != null) break; + } + + if (handle != null) + { + // One possible future consideration is to attempt cycling through to + // find one that might already have sufficient capacity + if (handle.ByteLength < minSize) + handle.Resize(minSize); + } + else + { + handle = new SafeHeapHandle(minSize); + } + + return handle; + } + + /// <summary> + /// Give a HeapHandle back for potential reuse + /// </summary> + public void Release(SafeHeapHandle handle) + { + if (handle.ByteLength <= _maxSize) + { + for (int i = 0; i < _handleCache.Length; i++) + { + // Push the handles down, walking the last one off the end to keep + // the top of the "stack" fresh + handle = Interlocked.Exchange(ref _handleCache[i], handle); + if (handle == null) return; + } + } + + handle.Dispose(); + } + + public void Dispose() + { + Dispose(disposing: true); + } + + private void Dispose(bool disposing) + { + if (disposing && _handleCache != null) + { + foreach (SafeHeapHandle handle in _handleCache) + { + if (handle != null) handle.Dispose(); + } + } + } + } +} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs new file mode 100644 index 0000000000..29cef08b6c --- /dev/null +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs @@ -0,0 +1,354 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + /// <summary> + /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger + /// than a maximum size string to enable working with very large string arrays. Always makes ordinal + /// comparisons. + /// + /// A more performant replacement for StringBuilder when performing native interop. + /// </summary> + /// <remarks> + /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle(). + /// </remarks> + internal class StringBuffer : NativeBuffer + { + private uint _length; + + /// <summary> + /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity + /// includes the trailing null character. + /// </summary> + public StringBuffer(uint initialCapacity = 0) + : base(initialCapacity * (ulong)sizeof(char)) + { + } + + /// <summary> + /// Get/set the character at the given index. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception> + public unsafe char this[uint index] + { + get + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + return CharPointer[index]; + } + set + { + if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); + CharPointer[index] = value; + } + } + + /// <summary> + /// Character capacity of the buffer. Includes the count for the trailing null character. + /// </summary> + public uint CharCapacity + { + get + { + ulong byteCapacity = ByteCapacity; + ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char); + return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity; + } + } + + /// <summary> + /// Ensure capacity in characters is at least the given minimum. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + public void EnsureCharCapacity(uint minCapacity) + { + EnsureByteCapacity(minCapacity * (ulong)sizeof(char)); + } + + /// <summary> + /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity. + /// This is where the usable data ends. + /// </summary> + /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> + /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null).</exception> + public unsafe uint Length + { + get { return _length; } + set + { + if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length)); + + // Null terminate + EnsureCharCapacity(value + 1); + CharPointer[value] = '\0'; + + _length = value; + } + } + + /// <summary> + /// For use when the native api null terminates but doesn't return a length. + /// If no null is found, the length will not be changed. + /// </summary> + public unsafe void SetLengthToFirstNull() + { + char* buffer = CharPointer; + uint capacity = CharCapacity; + for (uint i = 0; i < capacity; i++) + { + if (buffer[i] == '\0') + { + _length = i; + break; + } + } + } + + internal unsafe char* CharPointer + { + get + { + return (char*)VoidPointer; + } + } + + /// <summary> + /// True if the buffer contains the given character. + /// </summary> + public unsafe bool Contains(char value) + { + char* start = CharPointer; + uint length = _length; + + for (uint i = 0; i < length; i++) + { + if (*start++ == value) return true; + } + + return false; + } + + /// <summary> + /// Returns true if the buffer starts with the given string. + /// </summary> + public bool StartsWith(string value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (_length < (uint)value.Length) return false; + return SubstringEquals(value, startIndex: 0, count: value.Length); + } + + /// <summary> + /// Returns true if the specified StringBuffer substring equals the given value. + /// </summary> + /// <param name="value">The value to compare against the specified substring.</param> + /// <param name="startIndex">Start index of the sub string.</param> + /// <param name="count">Length of the substring, or -1 to check all remaining.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of the buffer's length. + /// </exception> + public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1) + { + if (value == null) return false; + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + + int length = value.Length; + + // Check the substring length against the input length + if (realCount != (uint)length) return false; + + fixed (char* valueStart = value) + { + char* bufferStart = CharPointer + startIndex; + for (int i = 0; i < length; i++) + { + // Note that indexing in this case generates faster code than trying to copy the pointer and increment it + if (*bufferStart++ != valueStart[i]) return false; + } + } + + return true; + } + + /// <summary> + /// Append the given string. + /// </summary> + /// <param name="value">The string to append.</param> + /// <param name="startIndex">The index in the input string to start appending from.</param> + /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(string value, int startIndex = 0, int count = -1) + { + CopyFrom( + bufferIndex: _length, + source: value, + sourceIndex: startIndex, + count: count); + } + + /// <summary> + /// Append the given buffer. + /// </summary> + /// <param name="value">The buffer to append.</param> + /// <param name="startIndex">The index in the input buffer to start appending from.</param> + /// <param name="count">The count of characters to copy from the buffer string.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(StringBuffer value, uint startIndex = 0) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (value.Length == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: value.Length); + } + + /// <summary> + /// Append the given buffer. + /// </summary> + /// <param name="value">The buffer to append.</param> + /// <param name="startIndex">The index in the input buffer to start appending from.</param> + /// <param name="count">The count of characters to copy from the buffer string.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + public void Append(StringBuffer value, uint startIndex, uint count) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (count == 0) return; + + value.CopyTo( + bufferIndex: startIndex, + destination: this, + destinationIndex: _length, + count: count); + } + + /// <summary> + /// Copy contents to the specified buffer. Destination index must be within current destination length. + /// Will grow the destination buffer if needed. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range + /// of <paramref name="value"/> characters. + /// </exception> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception> + public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); + if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = checked(destinationIndex + count); + if (destination._length < lastIndex) destination.Length = lastIndex; + + Buffer.MemoryCopy( + source: CharPointer + bufferIndex, + destination: destination.CharPointer + destinationIndex, + destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))), + sourceBytesToCopy: checked((long)count * sizeof(char))); + } + + /// <summary> + /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of + /// the buffer, will grow as necessary. + /// </summary> + public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); + if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); + if (count == -1) count = source.Length - sourceIndex; + if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count)); + + if (count == 0) return; + uint lastIndex = bufferIndex + (uint)count; + if (_length < lastIndex) Length = lastIndex; + + fixed (char* content = source) + { + Buffer.MemoryCopy( + source: content + sourceIndex, + destination: CharPointer + bufferIndex, + destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))), + sourceBytesToCopy: (long)count * sizeof(char)); + } + } + + /// <summary> + /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed. + /// </summary> + public unsafe void TrimEnd(char[] values) + { + if (values == null || values.Length == 0 || _length == 0) return; + + char* end = CharPointer + _length - 1; + + while (_length > 0 && Array.IndexOf(values, *end) >= 0) + { + Length = _length - 1; + end--; + } + } + + /// <summary> + /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw. + /// </summary> + /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception> + public unsafe override string ToString() + { + if (_length == 0) return string.Empty; + if (_length > int.MaxValue) throw new InvalidOperationException(); + return new string(CharPointer, startIndex: 0, length: (int)_length); + } + + /// <summary> + /// Get the given substring in the buffer. + /// </summary> + /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length + /// or count is greater than the maximum string size (int.MaxValue). + /// </exception> + public unsafe string Substring(uint startIndex, int count = -1) + { + if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); + + uint realCount = count == -1 ? _length - startIndex : (uint)count; + if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); + if (realCount == 0) return string.Empty; + + // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting + // index might be bigger than int we need to index ourselves. + return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount); + } + + public override void Free() + { + base.Free(); + _length = 0; + } + } +} |