summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/Runtime/InteropServices/NativeBuffer.cs
blob: 94261621c33c166aee29ef2adc5aeec8f559a0c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 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.
    /// </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
    {
        [System.Security.SecurityCritical]
        private readonly static SafeHandle s_emptyHandle;
        [System.Security.SecurityCritical]
        private SafeHeapHandle _handle;
        private ulong _capacity;

        [System.Security.SecuritySafeCritical]
        static NativeBuffer()
        {
            s_emptyHandle = new EmptySafeHandle();
        }

        /// <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)]
            [System.Security.SecurityCritical]
            get
            {
                return _handle == null ? null : _handle.DangerousGetHandle().ToPointer();
            }
        }

        protected unsafe byte* BytePointer
        {
            [System.Security.SecurityCritical]
            get
            {
                return (byte*)VoidPointer;
            }
        }

        /// <summary>
        /// Get the handle for the buffer.
        /// </summary>
        [System.Security.SecuritySafeCritical]
        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>
        [System.Security.SecuritySafeCritical]
        public void EnsureByteCapacity(ulong minCapacity)
        {
            if (_capacity < minCapacity)
            {
                Resize(minCapacity);
                _capacity = minCapacity;
            }
        }

        public unsafe byte this[ulong index]
        {
            [System.Security.SecuritySafeCritical]
            get
            {
                if (index >= _capacity) throw new ArgumentOutOfRangeException();
                return BytePointer[index];
            }
            [System.Security.SecuritySafeCritical]
            set
            {
                if (index >= _capacity) throw new ArgumentOutOfRangeException();
                BytePointer[index] = value;
            }
        }

        [System.Security.SecuritySafeCritical]
        private unsafe void Resize(ulong byteLength)
        {
            if (byteLength == 0)
            {
                ReleaseHandle();
                return;
            }

            if (_handle == null)
            {
                _handle = new SafeHeapHandle(byteLength);
            }
            else
            {
                _handle.Resize(byteLength);
            }
        }

        [System.Security.SecuritySafeCritical]
        private void ReleaseHandle()
        {
            if (_handle != null)
            {
                _capacity = 0;
                _handle = null;
            }
        }

        /// <summary>
        /// Release the backing buffer
        /// </summary>
        [System.Security.SecuritySafeCritical]
        public virtual void Free()
        {
            ReleaseHandle();
        }

        [System.Security.SecuritySafeCritical]
        public void Dispose()
        {
            Free();
        }

        [System.Security.SecurityCritical]
        private sealed class EmptySafeHandle : SafeHandle
        {
            public EmptySafeHandle() : base(IntPtr.Zero, true) { }

            public override bool IsInvalid
            {
                [System.Security.SecurityCritical]
                get
                { return true; }
            }

            [System.Security.SecurityCritical]
            protected override bool ReleaseHandle()
            {
                return true;
            }
        }
    }
}