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.
4 
5 extern alias System_Runtime_Extensions;
6 
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.Diagnostics.Contracts;
10 using System.Runtime.CompilerServices;
11 using System.Runtime.InteropServices;
12 using System.Threading.Tasks;
13 using Windows.Foundation;
14 using Windows.Storage.Streams;
15 using BufferedStream = System_Runtime_Extensions::System.IO.BufferedStream;
16 
17 namespace System.IO
18 {
19     /// <summary>
20     /// Contains extension methods for conversion between WinRT streams and managed streams.
21     /// This class is the public facade for the stream adapters library.
22     /// </summary>
23     public static class WindowsRuntimeStreamExtensions
24     {
25         #region Constants and static Fields
26 
27         private const Int32 DefaultBufferSize = 16384;  // = 0x4000 = 16 KBytes.
28 
29         private static ConditionalWeakTable<Object, Stream> s_winRtToNetFxAdapterMap
30                  = new ConditionalWeakTable<Object, Stream>();
31 
32         private static ConditionalWeakTable<Stream, NetFxToWinRtStreamAdapter> s_netFxToWinRtAdapterMap
33                  = new ConditionalWeakTable<Stream, NetFxToWinRtStreamAdapter>();
34 
35         #endregion Constants and static Fields
36 
37 
38         #region Helpers
39 
40 #if DEBUG
41         private static void AssertMapContains<TKey, TValue>(ConditionalWeakTable<TKey, TValue> map, TKey key, TValue value,
42                                                             bool valueMayBeWrappedInBufferedStream)
43                                                                                                 where TKey : class
44                                                                                                 where TValue : class
45         {
46             TValue valueInMap;
47 
48             Debug.Assert(key != null);
49 
50             bool hasValueForKey = map.TryGetValue(key, out valueInMap);
51 
Debug.Assert(hasValueForKey)52             Debug.Assert(hasValueForKey);
53 
54             if (valueMayBeWrappedInBufferedStream)
55             {
56                 BufferedStream bufferedValueInMap = valueInMap as BufferedStream;
57                 Debug.Assert(Object.ReferenceEquals(value, valueInMap)
58                                 || (bufferedValueInMap != null && Object.ReferenceEquals(value, bufferedValueInMap.UnderlyingStream)));
59             }
60             else
61             {
62                 Debug.Assert(Object.ReferenceEquals(value, valueInMap));
63             }
64         }
65 #endif  // DEBUG
66 
EnsureAdapterBufferSize(Stream adapter, Int32 requiredBufferSize, String methodName)67         private static void EnsureAdapterBufferSize(Stream adapter, Int32 requiredBufferSize, String methodName)
68         {
69             Debug.Assert(adapter != null);
70             Debug.Assert(!String.IsNullOrWhiteSpace(methodName));
71 
72             Int32 currentBufferSize = 0;
73             BufferedStream bufferedAdapter = adapter as BufferedStream;
74             if (bufferedAdapter != null)
75                 currentBufferSize = bufferedAdapter.BufferSize;
76 
77             if (requiredBufferSize != currentBufferSize)
78             {
79                 if (requiredBufferSize == 0)
80                     throw new InvalidOperationException(SR.Format(SR.InvalidOperation_CannotChangeBufferSizeOfWinRtStreamAdapterToZero, methodName));
81 
82                 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_CannotChangeBufferSizeOfWinRtStreamAdapter, methodName));
83             }
84         }
85 
86         #endregion Helpers
87 
88 
89         #region WinRt-to-NetFx conversion
90 
91         [CLSCompliant(false)]
AsStreamForRead(this IInputStream windowsRuntimeStream)92         public static Stream AsStreamForRead(this IInputStream windowsRuntimeStream)
93         {
94             return AsStreamInternal(windowsRuntimeStream, DefaultBufferSize, "AsStreamForRead", forceBufferSize: false);
95         }
96 
97 
98         [CLSCompliant(false)]
AsStreamForRead(this IInputStream windowsRuntimeStream, Int32 bufferSize)99         public static Stream AsStreamForRead(this IInputStream windowsRuntimeStream, Int32 bufferSize)
100         {
101             return AsStreamInternal(windowsRuntimeStream, bufferSize, "AsStreamForRead", forceBufferSize: true);
102         }
103 
104 
105         [CLSCompliant(false)]
AsStreamForWrite(this IOutputStream windowsRuntimeStream)106         public static Stream AsStreamForWrite(this IOutputStream windowsRuntimeStream)
107         {
108             return AsStreamInternal(windowsRuntimeStream, DefaultBufferSize, "AsStreamForWrite", forceBufferSize: false);
109         }
110 
111 
112         [CLSCompliant(false)]
AsStreamForWrite(this IOutputStream windowsRuntimeStream, Int32 bufferSize)113         public static Stream AsStreamForWrite(this IOutputStream windowsRuntimeStream, Int32 bufferSize)
114         {
115             return AsStreamInternal(windowsRuntimeStream, bufferSize, "AsStreamForWrite", forceBufferSize: true);
116         }
117 
118 
119         [CLSCompliant(false)]
AsStream(this IRandomAccessStream windowsRuntimeStream)120         public static Stream AsStream(this IRandomAccessStream windowsRuntimeStream)
121         {
122             return AsStreamInternal(windowsRuntimeStream, DefaultBufferSize, "AsStream", forceBufferSize: false);
123         }
124 
125 
126         [CLSCompliant(false)]
AsStream(this IRandomAccessStream windowsRuntimeStream, Int32 bufferSize)127         public static Stream AsStream(this IRandomAccessStream windowsRuntimeStream, Int32 bufferSize)
128         {
129             return AsStreamInternal(windowsRuntimeStream, bufferSize, "AsStream", forceBufferSize: true);
130         }
131 
132 
AsStreamInternal(Object windowsRuntimeStream, Int32 bufferSize, String invokedMethodName, bool forceBufferSize)133         private static Stream AsStreamInternal(Object windowsRuntimeStream, Int32 bufferSize, String invokedMethodName, bool forceBufferSize)
134         {
135             if (windowsRuntimeStream == null)
136                 throw new ArgumentNullException(nameof(windowsRuntimeStream));
137 
138             if (bufferSize < 0)
139                 throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_WinRtAdapterBufferSizeMayNotBeNegative);
140 
141             Debug.Assert(!String.IsNullOrWhiteSpace(invokedMethodName));
142             Contract.Ensures(Contract.Result<Stream>() != null);
143             Contract.EndContractBlock();
144 
145             // If the WinRT stream is actually a wrapped managed stream, we will unwrap it and return the original.
146             // In that case we do not need to put the wrapper into the map.
147 
148             // We currently do capability-based adapter selection for WinRt->NetFx, but not vice versa (time constraints).
149             // Once we added the reverse direction, we will be able replce this entire section with just a few lines.
150             NetFxToWinRtStreamAdapter sAdptr = windowsRuntimeStream as NetFxToWinRtStreamAdapter;
151             if (sAdptr != null)
152             {
153                 Stream wrappedNetFxStream = sAdptr.GetManagedStream();
154                 if (wrappedNetFxStream == null)
155                     throw new ObjectDisposedException(nameof(windowsRuntimeStream), SR.ObjectDisposed_CannotPerformOperation);
156 
157 #if DEBUG  // In Chk builds, verify that the original managed stream is correctly entered into the NetFx->WinRT map:
158                 AssertMapContains(s_netFxToWinRtAdapterMap, wrappedNetFxStream, sAdptr,
159                                   valueMayBeWrappedInBufferedStream: false);
160 #endif  // DEBUG
161 
162                 return wrappedNetFxStream;
163             }
164 
165             // We have a real WinRT stream.
166 
167             Stream adapter;
168             bool adapterExists = s_winRtToNetFxAdapterMap.TryGetValue(windowsRuntimeStream, out adapter);
169 
170             // There is already an adapter:
171             if (adapterExists)
172             {
173                 Debug.Assert((adapter is BufferedStream && ((BufferedStream)adapter).UnderlyingStream is WinRtToNetFxStreamAdapter)
174                                 || (adapter is WinRtToNetFxStreamAdapter));
175 
176                 if (forceBufferSize)
177                     EnsureAdapterBufferSize(adapter, bufferSize, invokedMethodName);
178 
179                 return adapter;
180             }
181 
182             // We do not have an adapter for this WinRT stream yet and we need to create one.
183             // Do that in a thread-safe manner in a separate method such that we only have to pay for the compiler allocating
184             // the required closure if this code path is hit:
185 
186             return AsStreamInternalFactoryHelper(windowsRuntimeStream, bufferSize, invokedMethodName, forceBufferSize);
187         }
188 
189 
190         // Separate method so we only pay for closure allocation if this code is executed:
WinRtToNetFxAdapterMap_GetValue(Object winRtStream)191         private static Stream WinRtToNetFxAdapterMap_GetValue(Object winRtStream)
192         {
193             return s_winRtToNetFxAdapterMap.GetValue(winRtStream, (wrtStr) => WinRtToNetFxStreamAdapter.Create(wrtStr));
194         }
195 
196 
197         // Separate method so we only pay for closure allocation if this code is executed:
WinRtToNetFxAdapterMap_GetValue(Object winRtStream, Int32 bufferSize)198         private static Stream WinRtToNetFxAdapterMap_GetValue(Object winRtStream, Int32 bufferSize)
199         {
200             return s_winRtToNetFxAdapterMap.GetValue(winRtStream, (wrtStr) => new BufferedStream(WinRtToNetFxStreamAdapter.Create(wrtStr), bufferSize));
201         }
202 
203 
AsStreamInternalFactoryHelper(Object windowsRuntimeStream, Int32 bufferSize, String invokedMethodName, bool forceBufferSize)204         private static Stream AsStreamInternalFactoryHelper(Object windowsRuntimeStream, Int32 bufferSize, String invokedMethodName, bool forceBufferSize)
205         {
206             Debug.Assert(windowsRuntimeStream != null);
207             Debug.Assert(bufferSize >= 0);
208             Debug.Assert(!String.IsNullOrWhiteSpace(invokedMethodName));
209 
210             Contract.Ensures(Contract.Result<Stream>() != null);
211             Contract.EndContractBlock();
212 
213             // Get the adapter for this windowsRuntimeStream again (it may have been created concurrently).
214             // If none exists yet, create a new one:
215             Stream adapter = (bufferSize == 0)
216                                 ? WinRtToNetFxAdapterMap_GetValue(windowsRuntimeStream)
217                                 : WinRtToNetFxAdapterMap_GetValue(windowsRuntimeStream, bufferSize);
218 
219             Debug.Assert(adapter != null);
220             Debug.Assert((adapter is BufferedStream && ((BufferedStream)adapter).UnderlyingStream is WinRtToNetFxStreamAdapter)
221                                 || (adapter is WinRtToNetFxStreamAdapter));
222 
223             if (forceBufferSize)
224                 EnsureAdapterBufferSize(adapter, bufferSize, invokedMethodName);
225 
226             WinRtToNetFxStreamAdapter actualAdapter = adapter as WinRtToNetFxStreamAdapter;
227             if (actualAdapter == null)
228                 actualAdapter = ((BufferedStream)adapter).UnderlyingStream as WinRtToNetFxStreamAdapter;
229 
230             actualAdapter.SetWonInitializationRace();
231 
232             return adapter;
233         }
234 
235         #endregion WinRt-to-NetFx conversion
236 
237 
238         #region NetFx-to-WinRt conversion
239 
240         [CLSCompliant(false)]
AsInputStream(this Stream stream)241         public static IInputStream AsInputStream(this Stream stream)
242         {
243             if (stream == null)
244                 throw new ArgumentNullException(nameof(stream));
245 
246             if (!stream.CanRead)
247                 throw new NotSupportedException(SR.NotSupported_CannotConvertNotReadableToInputStream);
248 
249             Contract.Ensures(Contract.Result<IInputStream>() != null);
250             Contract.EndContractBlock();
251 
252             Object adapter = AsWindowsRuntimeStreamInternal(stream);
253 
254             IInputStream winRtStream = adapter as IInputStream;
255             Debug.Assert(winRtStream != null);
256 
257             return winRtStream;
258         }
259 
260 
261         [CLSCompliant(false)]
AsOutputStream(this Stream stream)262         public static IOutputStream AsOutputStream(this Stream stream)
263         {
264             if (stream == null)
265                 throw new ArgumentNullException(nameof(stream));
266 
267             if (!stream.CanWrite)
268                 throw new NotSupportedException(SR.NotSupported_CannotConvertNotWritableToOutputStream);
269 
270             Contract.Ensures(Contract.Result<IOutputStream>() != null);
271             Contract.EndContractBlock();
272 
273             Object adapter = AsWindowsRuntimeStreamInternal(stream);
274 
275             IOutputStream winRtStream = adapter as IOutputStream;
276             Debug.Assert(winRtStream != null);
277 
278             return winRtStream;
279         }
280 
281 
282         [CLSCompliant(false)]
AsRandomAccessStream(this Stream stream)283         public static IRandomAccessStream AsRandomAccessStream(this Stream stream)
284         {
285             if (stream == null)
286                 throw new ArgumentNullException(nameof(stream));
287 
288             if (!stream.CanSeek)
289                 throw new NotSupportedException(SR.NotSupported_CannotConvertNotSeekableToRandomAccessStream);
290 
291             Contract.Ensures(Contract.Result<IRandomAccessStream>() != null);
292             Contract.EndContractBlock();
293 
294             Object adapter = AsWindowsRuntimeStreamInternal(stream);
295 
296             IRandomAccessStream winRtStream = adapter as IRandomAccessStream;
297             Debug.Assert(winRtStream != null);
298 
299             return winRtStream;
300         }
301 
302 
AsWindowsRuntimeStreamInternal(Stream stream)303         private static Object AsWindowsRuntimeStreamInternal(Stream stream)
304         {
305             Contract.Ensures(Contract.Result<Object>() != null);
306             Contract.EndContractBlock();
307 
308             // Check to see if the managed stream is actually a wrapper of a WinRT stream:
309             // (This can be either an adapter directly, or an adapter wrapped in a BufferedStream.)
310             WinRtToNetFxStreamAdapter sAdptr = stream as WinRtToNetFxStreamAdapter;
311             if (sAdptr == null)
312             {
313                 BufferedStream buffAdptr = stream as BufferedStream;
314                 if (buffAdptr != null)
315                     sAdptr = buffAdptr.UnderlyingStream as WinRtToNetFxStreamAdapter;
316             }
317 
318             // If the managed stream us actually a WinRT stream, we will unwrap it and return the original.
319             // In that case we do not need to put the wrapper into the map.
320             if (sAdptr != null)
321             {
322                 Object wrappedWinRtStream = sAdptr.GetWindowsRuntimeStream<Object>();
323                 if (wrappedWinRtStream == null)
324                     throw new ObjectDisposedException(nameof(stream), SR.ObjectDisposed_CannotPerformOperation);
325 
326 #if DEBUG  // In Chk builds, verify that the original WinRT stream is correctly entered into the WinRT->NetFx map:
327                 AssertMapContains(s_winRtToNetFxAdapterMap, wrappedWinRtStream, sAdptr, valueMayBeWrappedInBufferedStream: true);
328 #endif  // DEBUG
329                 return wrappedWinRtStream;
330             }
331 
332             // We have a real managed Stream.
333 
334             // See if the managed stream already has an adapter:
335             NetFxToWinRtStreamAdapter adapter;
336             bool adapterExists = s_netFxToWinRtAdapterMap.TryGetValue(stream, out adapter);
337 
338             // There is already an adapter:
339             if (adapterExists)
340                 return adapter;
341 
342             // We do not have an adapter for this managed stream yet and we need to create one.
343             // Do that in a thread-safe manner in a separate method such that we only have to pay for the compiler allocating
344             // the required closure if this code path is hit:
345             return AsWindowsRuntimeStreamInternalFactoryHelper(stream);
346         }
347 
348 
AsWindowsRuntimeStreamInternalFactoryHelper(Stream stream)349         private static NetFxToWinRtStreamAdapter AsWindowsRuntimeStreamInternalFactoryHelper(Stream stream)
350         {
351             Debug.Assert(stream != null);
352             Contract.Ensures(Contract.Result<NetFxToWinRtStreamAdapter>() != null);
353             Contract.EndContractBlock();
354 
355             // Get the adapter for managed stream again (it may have been created concurrently).
356             // If none exists yet, create a new one:
357             NetFxToWinRtStreamAdapter adapter = s_netFxToWinRtAdapterMap.GetValue(stream, (str) => NetFxToWinRtStreamAdapter.Create(str));
358 
359             Debug.Assert(adapter != null);
360             adapter.SetWonInitializationRace();
361 
362             return adapter;
363         }
364         #endregion NetFx-to-WinRt conversion
365 
366     }  // class WindowsRuntimeStreamExtensions
367 }  // namespace
368 
369 // WindowsRuntimeStreamExtensions.cs
370