1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 /*============================================================
7 ** Purpose: Unsafe code that uses pointers should use
8 ** SafePointer to fix subtle lifetime problems with the
9 ** underlying resource.
11 ===========================================================*/
14 // *) Avoid handle-recycling problems (including ones triggered via
15 // resurrection attacks) for all accesses via pointers. This requires tying
16 // together the lifetime of the unmanaged resource with the code that reads
17 // from that resource, in a package that uses synchronization to enforce
18 // the correct semantics during finalization. We're using SafeHandle's
19 // ref count as a gate on whether the pointer can be dereferenced because that
20 // controls the lifetime of the resource.
22 // *) Keep the penalties for using this class small, both in terms of space
23 // and time. Having multiple threads reading from a memory mapped file
24 // will already require 2 additional interlocked operations. If we add in
25 // a "current position" concept, that requires additional space in memory and
26 // synchronization. Since the position in memory is often (but not always)
27 // something that can be stored on the stack, we can save some memory by
28 // excluding it from this object. However, avoiding the need for
29 // synchronization is a more significant win. This design allows multiple
30 // threads to read and write memory simultaneously without locks (as long as
31 // you don't write to a region of memory that overlaps with what another
32 // thread is accessing).
34 // *) Space-wise, we use the following memory, including SafeHandle's fields:
35 // Object Header MT* handle int bool bool <2 pad bytes> length
36 // On 32 bit platforms: 24 bytes. On 64 bit platforms: 40 bytes.
37 // (We can safe 4 bytes on x86 only by shrinking SafeHandle)
39 // *) Wrapping a SafeHandle would have been a nice solution, but without an
40 // ordering between critical finalizable objects, it would have required
41 // changes to each SafeHandle subclass to opt in to being usable from a
42 // SafeBuffer (or some clever exposure of SafeHandle's state fields and a
43 // way of forcing ReleaseHandle to run even after the SafeHandle has been
44 // finalized with a ref count > 1). We can use less memory and create fewer
45 // objects by simply inserting a SafeBuffer into the class hierarchy.
47 // *) In an ideal world, we could get marshaling support for SafeBuffer that
48 // would allow us to annotate a P/Invoke declaration, saying this parameter
49 // specifies the length of the buffer, and the units of that length are X.
50 // P/Invoke would then pass that size parameter to SafeBuffer.
52 // static extern SafeMemoryHandle AllocCharBuffer(int numChars);
53 // If we could put an attribute on the SafeMemoryHandle saying numChars is
54 // the element length, and it must be multiplied by 2 to get to the byte
55 // length, we can simplify the usage model for SafeBuffer.
57 // *) This class could benefit from a constraint saying T is a value type
58 // containing no GC references.
60 // Implementation notes:
61 // *) The Initialize method must be called before you use any instance of
62 // a SafeBuffer. To avoid race conditions when storing SafeBuffers in statics,
63 // you either need to take a lock when publishing the SafeBuffer, or you
64 // need to create a local, initialize the SafeBuffer, then assign to the
65 // static variable (perhaps using Interlocked.CompareExchange). Of course,
66 // assignments in a static class constructor are under a lock implicitly.
69 using System.Diagnostics;
70 using System.Runtime.CompilerServices;
71 using Internal.Runtime.CompilerServices;
72 using Microsoft.Win32.SafeHandles;
74 namespace System.Runtime.InteropServices
76 public abstract unsafe class SafeBuffer : SafeHandleZeroOrMinusOneIsInvalid
78 // Steal UIntPtr.MaxValue as our uninitialized value.
79 private static readonly UIntPtr Uninitialized = (UIntPtr.Size == 4) ?
80 ((UIntPtr)uint.MaxValue) : ((UIntPtr)ulong.MaxValue);
82 private UIntPtr _numBytes;
84 protected SafeBuffer(bool ownsHandle) : base(ownsHandle)
86 _numBytes = Uninitialized;
90 /// Specifies the size of the region of memory, in bytes. Must be
91 /// called before using the SafeBuffer.
93 /// <param name="numBytes">Number of valid bytes in memory.</param>
95 public void Initialize(ulong numBytes)
97 if (IntPtr.Size == 4 && numBytes > uint.MaxValue)
98 throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_AddressSpace);
100 if (numBytes >= (ulong)Uninitialized)
101 throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_UIntPtrMax);
103 _numBytes = (UIntPtr)numBytes;
107 /// Specifies the size of the region in memory, as the number of
108 /// elements in an array. Must be called before using the SafeBuffer.
110 [CLSCompliant(false)]
111 public void Initialize(uint numElements, uint sizeOfEachElement)
113 if (sizeOfEachElement == 0)
115 _numBytes = (UIntPtr)0;
119 if (IntPtr.Size == 4 && numElements > uint.MaxValue / sizeOfEachElement)
120 throw new ArgumentOutOfRangeException("numBytes", SR.ArgumentOutOfRange_AddressSpace);
122 if (numElements >= (ulong)Uninitialized / sizeOfEachElement)
123 throw new ArgumentOutOfRangeException(nameof(numElements), SR.ArgumentOutOfRange_UIntPtrMax);
125 _numBytes = checked((UIntPtr)(numElements * sizeOfEachElement));
130 /// Specifies the size of the region in memory, as the number of
131 /// elements in an array. Must be called before using the SafeBuffer.
133 [CLSCompliant(false)]
134 public void Initialize<T>(uint numElements) where T : struct
136 Initialize(numElements, AlignedSizeOf<T>());
139 // Callers should ensure that they check whether the pointer ref param
140 // is null when AcquirePointer returns. If it is not null, they must
141 // call ReleasePointer. This method calls DangerousAddRef
142 // & exposes the pointer. Unlike Read, it does not alter the "current
143 // position" of the pointer. Here's how to use it:
145 // byte* pointer = null;
147 // safeBuffer.AcquirePointer(ref pointer);
148 // // Use pointer here, with your own bounds checking
151 // if (pointer != null)
152 // safeBuffer.ReleasePointer();
155 // Note: If you cast this byte* to a T*, you have to worry about
156 // whether your pointer is aligned. Additionally, you must take
157 // responsibility for all bounds checking with this pointer.
159 /// Obtain the pointer from a SafeBuffer for a block of code,
160 /// with the express responsibility for bounds checking and calling
161 /// ReleasePointer later to ensure the pointer can be freed later.
162 /// This method either completes successfully or throws an exception
163 /// and returns with pointer set to null.
165 /// <param name="pointer">A byte*, passed by reference, to receive
166 /// the pointer from within the SafeBuffer. You must set
167 /// pointer to null before calling this method.</param>
168 [CLSCompliant(false)]
169 public void AcquirePointer(ref byte* pointer)
171 if (_numBytes == Uninitialized)
172 throw NotInitialized();
177 DangerousAddRef(ref junk);
178 pointer = (byte*)handle;
181 public void ReleasePointer()
183 if (_numBytes == Uninitialized)
184 throw NotInitialized();
190 /// Read a value type from memory at the given offset. This is
191 /// equivalent to: return *(T*)(bytePtr + byteOffset);
193 /// <typeparam name="T">The value type to read</typeparam>
194 /// <param name="byteOffset">Where to start reading from memory. You
195 /// may have to consider alignment.</param>
196 /// <returns>An instance of T read from memory.</returns>
197 [CLSCompliant(false)]
198 public T Read<T>(ulong byteOffset) where T : struct
200 if (_numBytes == Uninitialized)
201 throw NotInitialized();
203 uint sizeofT = SizeOf<T>();
204 byte* ptr = (byte*)handle + byteOffset;
205 SpaceCheck(ptr, sizeofT);
207 // return *(T*) (_ptr + byteOffset);
209 bool mustCallRelease = false;
212 DangerousAddRef(ref mustCallRelease);
214 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
215 Buffer.Memmove(pStructure, ptr, sizeofT);
225 [CLSCompliant(false)]
226 public void ReadArray<T>(ulong byteOffset, T[] array, int index, int count)
230 throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
232 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
234 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
235 if (array.Length - index < count)
236 throw new ArgumentException(SR.Argument_InvalidOffLen);
238 if (_numBytes == Uninitialized)
239 throw NotInitialized();
241 uint sizeofT = SizeOf<T>();
242 uint alignedSizeofT = AlignedSizeOf<T>();
243 byte* ptr = (byte*)handle + byteOffset;
244 SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
246 bool mustCallRelease = false;
249 DangerousAddRef(ref mustCallRelease);
255 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
257 for (int i = 0; i < count; i++)
258 Buffer.Memmove(pStructure + sizeofT * i, ptr + alignedSizeofT * i, sizeofT);
271 /// Write a value type to memory at the given offset. This is
272 /// equivalent to: *(T*)(bytePtr + byteOffset) = value;
274 /// <typeparam name="T">The type of the value type to write to memory.</typeparam>
275 /// <param name="byteOffset">The location in memory to write to. You
276 /// may have to consider alignment.</param>
277 /// <param name="value">The value type to write to memory.</param>
278 [CLSCompliant(false)]
279 public void Write<T>(ulong byteOffset, T value) where T : struct
281 if (_numBytes == Uninitialized)
282 throw NotInitialized();
284 uint sizeofT = SizeOf<T>();
285 byte* ptr = (byte*)handle + byteOffset;
286 SpaceCheck(ptr, sizeofT);
288 // *((T*) (_ptr + byteOffset)) = value;
289 bool mustCallRelease = false;
292 DangerousAddRef(ref mustCallRelease);
294 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
295 Buffer.Memmove(ptr, pStructure, sizeofT);
304 [CLSCompliant(false)]
305 public void WriteArray<T>(ulong byteOffset, T[] array, int index, int count)
309 throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
311 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
313 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
314 if (array.Length - index < count)
315 throw new ArgumentException(SR.Argument_InvalidOffLen);
317 if (_numBytes == Uninitialized)
318 throw NotInitialized();
320 uint sizeofT = SizeOf<T>();
321 uint alignedSizeofT = AlignedSizeOf<T>();
322 byte* ptr = (byte*)handle + byteOffset;
323 SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
325 bool mustCallRelease = false;
328 DangerousAddRef(ref mustCallRelease);
334 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
336 for (int i = 0; i < count; i++)
337 Buffer.Memmove(ptr + alignedSizeofT * i, pStructure + sizeofT * i, sizeofT);
350 /// Returns the number of bytes in the memory region.
352 [CLSCompliant(false)]
353 public ulong ByteLength
357 if (_numBytes == Uninitialized)
358 throw NotInitialized();
360 return (ulong)_numBytes;
364 /* No indexer. The perf would be misleadingly bad. People should use
365 * AcquirePointer and ReleasePointer instead. */
367 private void SpaceCheck(byte* ptr, ulong sizeInBytes)
369 if ((ulong)_numBytes < sizeInBytes)
371 if ((ulong)(ptr - (byte*)handle) > ((ulong)_numBytes) - sizeInBytes)
375 private static void NotEnoughRoom()
377 throw new ArgumentException(SR.Arg_BufferTooSmall);
380 private static InvalidOperationException NotInitialized()
382 return new InvalidOperationException(SR.InvalidOperation_MustCallInitialize);
386 /// Returns the size that SafeBuffer (and hence, UnmanagedMemoryAccessor) reserves in the unmanaged buffer for each element of an array of T. This is not the same
387 /// value that sizeof(T) returns! Since the primary use case is to parse memory mapped files, we cannot change this algorithm as this defines a de-facto serialization format.
388 /// Throws if T contains GC references.
390 internal static uint AlignedSizeOf<T>() where T : struct
392 uint size = SizeOf<T>();
393 if (size == 1 || size == 2)
398 return (uint)(((size + 3) & (~3)));
402 /// Returns same value as sizeof(T) but throws if T contains GC references.
404 internal static uint SizeOf<T>() where T : struct
406 if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
407 throw new ArgumentException(SR.Argument_NeedStructWithNoRefs);
409 return (uint)Unsafe.SizeOf<T>();