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