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 using System.Diagnostics; 6 using System.Diagnostics.Contracts; 7 using System.IO; 8 using System.Runtime.InteropServices.WindowsRuntime; 9 using System.Runtime.InteropServices; 10 using System.Threading.Tasks; 11 using System.Threading; 12 using Windows.Foundation; 13 using Windows.Storage.Streams; 14 15 namespace System.IO 16 { 17 /// <summary> 18 /// An <code>wrapper</code> for a managed stream that implements all WinRT stream operations. 19 /// This class must not implement any WinRT stream interfaces directly. 20 /// We never create instances of this class directly; instead we use classes defined in 21 /// the region Interface adapters to implement WinRT ifaces and create instances of those types. 22 /// See comment in that region for technical details. 23 /// </summary> 24 internal abstract class NetFxToWinRtStreamAdapter : IDisposable 25 { 26 #region Construction 27 28 #region Interface adapters 29 30 // Instances of private types defined in this section will be returned from NetFxToWinRtStreamAdapter.Create(..). 31 // Depending on the capabilities of the .NET stream for which we need to construct the adapter, we need to return 32 // an object that can be QIed (COM speak for "cast") to a well-defined set of ifaces. 33 // E.g, if the specified stream CanRead, but not CanSeek and not CanWrite, then we *must* return an object that 34 // can be QIed to IInputStream, but *not* IRandomAccessStream and *not* IOutputStream. 35 // There are two ways to do that: 36 // - We could explicitly implement ICustomQueryInterface and respond to QI requests by analyzing the stream capabilities 37 // - We can use the runtime's ability to do that for us, based on the ifaces the concrete class implements (or does not). 38 // The latter is much more elegant, and likely also faster. 39 40 41 private class InputStream : NetFxToWinRtStreamAdapter, IInputStream, IDisposable 42 { InputStream(Stream stream, StreamReadOperationOptimization readOptimization)43 internal InputStream(Stream stream, StreamReadOperationOptimization readOptimization) 44 : base(stream, readOptimization) 45 { 46 } 47 } 48 49 50 private class OutputStream : NetFxToWinRtStreamAdapter, IOutputStream, IDisposable 51 { OutputStream(Stream stream, StreamReadOperationOptimization readOptimization)52 internal OutputStream(Stream stream, StreamReadOperationOptimization readOptimization) 53 : base(stream, readOptimization) 54 { 55 } 56 } 57 58 59 private class RandomAccessStream : NetFxToWinRtStreamAdapter, IRandomAccessStream, IInputStream, IOutputStream, IDisposable 60 { RandomAccessStream(Stream stream, StreamReadOperationOptimization readOptimization)61 internal RandomAccessStream(Stream stream, StreamReadOperationOptimization readOptimization) 62 : base(stream, readOptimization) 63 { 64 } 65 } 66 67 68 private class InputOutputStream : NetFxToWinRtStreamAdapter, IInputStream, IOutputStream, IDisposable 69 { InputOutputStream(Stream stream, StreamReadOperationOptimization readOptimization)70 internal InputOutputStream(Stream stream, StreamReadOperationOptimization readOptimization) 71 : base(stream, readOptimization) 72 { 73 } 74 } 75 76 #endregion Interface adapters 77 78 // We may want to define different behaviour for different types of streams. 79 // For instance, ReadAsync treats MemoryStream special for performance reasons. 80 // The enum 'StreamReadOperationOptimization' describes the read optimization to employ for a 81 // given NetFxToWinRtStreamAdapter instance. In future, we might define other enums to follow a 82 // similar pattern, e.g. 'StreamWriteOperationOptimization' or 'StreamFlushOperationOptimization'. 83 private enum StreamReadOperationOptimization 84 { 85 AbstractStream = 0, MemoryStream 86 } 87 88 Create(Stream stream)89 internal static NetFxToWinRtStreamAdapter Create(Stream stream) 90 { 91 if (stream == null) 92 throw new ArgumentNullException(nameof(stream)); 93 94 StreamReadOperationOptimization readOptimization = StreamReadOperationOptimization.AbstractStream; 95 if (stream.CanRead) 96 readOptimization = DetermineStreamReadOptimization(stream); 97 98 NetFxToWinRtStreamAdapter adapter; 99 100 if (stream.CanSeek) 101 adapter = new RandomAccessStream(stream, readOptimization); 102 103 else if (stream.CanRead && stream.CanWrite) 104 adapter = new InputOutputStream(stream, readOptimization); 105 106 else if (stream.CanRead) 107 adapter = new InputStream(stream, readOptimization); 108 109 else if (stream.CanWrite) 110 adapter = new OutputStream(stream, readOptimization); 111 112 else 113 throw new ArgumentException(SR.Argument_NotSufficientCapabilitiesToConvertToWinRtStream); 114 115 return adapter; 116 } 117 118 DetermineStreamReadOptimization(Stream stream)119 private static StreamReadOperationOptimization DetermineStreamReadOptimization(Stream stream) 120 { 121 Debug.Assert(stream != null); 122 123 if (CanApplyReadMemoryStreamOptimization(stream)) 124 return StreamReadOperationOptimization.MemoryStream; 125 126 return StreamReadOperationOptimization.AbstractStream; 127 } 128 129 CanApplyReadMemoryStreamOptimization(Stream stream)130 private static bool CanApplyReadMemoryStreamOptimization(Stream stream) 131 { 132 MemoryStream memStream = stream as MemoryStream; 133 if (memStream == null) 134 return false; 135 136 ArraySegment<byte> arrSeg; 137 return memStream.TryGetBuffer(out arrSeg); 138 } 139 140 NetFxToWinRtStreamAdapter(Stream stream, StreamReadOperationOptimization readOptimization)141 private NetFxToWinRtStreamAdapter(Stream stream, StreamReadOperationOptimization readOptimization) 142 { 143 Debug.Assert(stream != null); 144 Debug.Assert(stream.CanRead || stream.CanWrite || stream.CanSeek); 145 Contract.EndContractBlock(); 146 147 Debug.Assert(!stream.CanRead || (stream.CanRead && this is IInputStream)); 148 Debug.Assert(!stream.CanWrite || (stream.CanWrite && this is IOutputStream)); 149 Debug.Assert(!stream.CanSeek || (stream.CanSeek && this is IRandomAccessStream)); 150 151 _readOptimization = readOptimization; 152 _managedStream = stream; 153 } 154 155 #endregion Construction 156 157 158 #region Instance variables 159 160 private Stream _managedStream = null; 161 private bool _leaveUnderlyingStreamOpen = true; 162 private readonly StreamReadOperationOptimization _readOptimization; 163 164 #endregion Instance variables 165 166 167 #region Tools and Helpers 168 169 /// <summary> 170 /// We keep tables for mappings between managed and WinRT streams to make sure to always return the same adapter for a given underlying stream. 171 /// However, in order to avoid global locks on those tables, several instances of this type may be created and then can race to be entered 172 /// into the appropriate map table. All except for the winning instances will be thrown away. However, we must ensure that when the losers are 173 /// finalized, they do not dispose the underlying stream. To ensure that, we must call this method on the winner to notify it that it is safe to 174 /// dispose the underlying stream. 175 /// </summary> SetWonInitializationRace()176 internal void SetWonInitializationRace() 177 { 178 _leaveUnderlyingStreamOpen = false; 179 } 180 181 GetManagedStream()182 public Stream GetManagedStream() 183 { 184 return _managedStream; 185 } 186 187 EnsureNotDisposed()188 private Stream EnsureNotDisposed() 189 { 190 Stream str = _managedStream; 191 192 if (str == null) 193 { 194 ObjectDisposedException ex = new ObjectDisposedException(SR.ObjectDisposed_CannotPerformOperation); 195 ex.SetErrorCode(__HResults.RO_E_CLOSED); 196 throw ex; 197 } 198 199 return str; 200 } 201 202 #endregion Tools and Helpers 203 204 205 #region Common public interface 206 207 /// <summary>Implements IDisposable.Dispose (IClosable.Close in WinRT)</summary> IDisposable.Dispose()208 void IDisposable.Dispose() 209 { 210 Stream str = _managedStream; 211 if (str == null) 212 return; 213 214 _managedStream = null; 215 216 if (!_leaveUnderlyingStreamOpen) 217 str.Dispose(); 218 } 219 220 #endregion Common public interface 221 222 223 #region IInputStream public interface 224 ReadAsync(IBuffer buffer, UInt32 count, InputStreamOptions options)225 public IAsyncOperationWithProgress<IBuffer, UInt32> ReadAsync(IBuffer buffer, UInt32 count, InputStreamOptions options) 226 { 227 if (buffer == null) 228 { 229 // Mapped to E_POINTER. 230 throw new ArgumentNullException(nameof(buffer)); 231 } 232 233 if (count < 0 || Int32.MaxValue < count) 234 { 235 ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException(nameof(count)); 236 ex.SetErrorCode(__HResults.E_INVALIDARG); 237 throw ex; 238 } 239 240 if (buffer.Capacity < count) 241 { 242 ArgumentException ex = new ArgumentException(SR.Argument_InsufficientBufferCapacity); 243 ex.SetErrorCode(__HResults.E_INVALIDARG); 244 throw ex; 245 } 246 247 if (!(options == InputStreamOptions.None || options == InputStreamOptions.Partial || options == InputStreamOptions.ReadAhead)) 248 { 249 ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException(nameof(options), 250 SR.ArgumentOutOfRange_InvalidInputStreamOptionsEnumValue); 251 ex.SetErrorCode(__HResults.E_INVALIDARG); 252 throw ex; 253 } 254 255 // Commented due to a reported CCRewrite bug. Should uncomment when fixed: 256 //Contract.Ensures(Contract.Result<IAsyncOperationWithProgress<IBuffer, UInt32>>() != null); 257 //Contract.EndContractBlock(); 258 259 Stream str = EnsureNotDisposed(); 260 261 IAsyncOperationWithProgress<IBuffer, UInt32> readAsyncOperation; 262 switch (_readOptimization) 263 { 264 case StreamReadOperationOptimization.MemoryStream: 265 readAsyncOperation = StreamOperationsImplementation.ReadAsync_MemoryStream(str, buffer, count); 266 break; 267 268 case StreamReadOperationOptimization.AbstractStream: 269 readAsyncOperation = StreamOperationsImplementation.ReadAsync_AbstractStream(str, buffer, count, options); 270 break; 271 272 // Use this pattern to add more optimisation options if necessary: 273 //case StreamReadOperationOptimization.XxxxStream: 274 // readAsyncOperation = StreamOperationsImplementation.ReadAsync_XxxxStream(str, buffer, count, options); 275 // break; 276 277 default: 278 Debug.Assert(false, "We should never get here. Someone forgot to handle an input stream optimisation option."); 279 readAsyncOperation = null; 280 break; 281 } 282 283 return readAsyncOperation; 284 } 285 286 #endregion IInputStream public interface 287 288 289 #region IOutputStream public interface 290 WriteAsync(IBuffer buffer)291 public IAsyncOperationWithProgress<UInt32, UInt32> WriteAsync(IBuffer buffer) 292 { 293 if (buffer == null) 294 { 295 // Mapped to E_POINTER. 296 throw new ArgumentNullException(nameof(buffer)); 297 } 298 299 if (buffer.Capacity < buffer.Length) 300 { 301 ArgumentException ex = new ArgumentException(SR.Argument_BufferLengthExceedsCapacity); 302 ex.SetErrorCode(__HResults.E_INVALIDARG); 303 throw ex; 304 } 305 306 // Commented due to a reported CCRewrite bug. Should uncomment when fixed: 307 //Contract.Ensures(Contract.Result<IAsyncOperationWithProgress<UInt32, UInt32>>() != null); 308 //Contract.EndContractBlock(); 309 310 Stream str = EnsureNotDisposed(); 311 return StreamOperationsImplementation.WriteAsync_AbstractStream(str, buffer); 312 } 313 314 FlushAsync()315 public IAsyncOperation<Boolean> FlushAsync() 316 { 317 Contract.Ensures(Contract.Result<IAsyncOperation<Boolean>>() != null); 318 Contract.EndContractBlock(); 319 320 Stream str = EnsureNotDisposed(); 321 return StreamOperationsImplementation.FlushAsync_AbstractStream(str); 322 } 323 324 #endregion IOutputStream public interface 325 326 327 #region IRandomAccessStream public interface 328 329 330 #region IRandomAccessStream public interface: Not cloning related 331 Seek(UInt64 position)332 public void Seek(UInt64 position) 333 { 334 if (position > Int64.MaxValue) 335 { 336 ArgumentException ex = new ArgumentException(SR.IO_CannotSeekBeyondInt64MaxValue); 337 ex.SetErrorCode(__HResults.E_INVALIDARG); 338 throw ex; 339 } 340 341 // Commented due to a reported CCRewrite bug. Should uncomment when fixed: 342 //Contract.EndContractBlock(); 343 344 Stream str = EnsureNotDisposed(); 345 Int64 pos = unchecked((Int64)position); 346 347 Debug.Assert(str != null); 348 Debug.Assert(str.CanSeek, "The underlying str is expected to support Seek, but it does not."); 349 Debug.Assert(0 <= pos, "Unexpected pos=" + pos + "."); 350 351 str.Seek(pos, SeekOrigin.Begin); 352 } 353 354 355 public bool CanRead 356 { 357 get 358 { 359 Stream str = EnsureNotDisposed(); 360 return str.CanRead; 361 } 362 } 363 364 365 public bool CanWrite 366 { 367 get 368 { 369 Stream str = EnsureNotDisposed(); 370 return str.CanWrite; 371 } 372 } 373 374 375 public UInt64 Position 376 { 377 get 378 { 379 Contract.Ensures(Contract.Result<UInt64>() >= 0); 380 381 Stream str = EnsureNotDisposed(); 382 return (UInt64)str.Position; 383 } 384 } 385 386 387 public UInt64 Size 388 { 389 get 390 { 391 Contract.Ensures(Contract.Result<UInt64>() >= 0); 392 393 Stream str = EnsureNotDisposed(); 394 return (UInt64)str.Length; 395 } 396 397 set 398 { 399 if (value > Int64.MaxValue) 400 { 401 ArgumentException ex = new ArgumentException(SR.IO_CannotSetSizeBeyondInt64MaxValue); 402 ex.SetErrorCode(__HResults.E_INVALIDARG); 403 throw ex; 404 } 405 406 // Commented due to a reported CCRewrite bug. Should uncomment when fixed: 407 //Contract.EndContractBlock(); 408 409 Stream str = EnsureNotDisposed(); 410 411 if (!str.CanWrite) 412 { 413 InvalidOperationException ex = new InvalidOperationException(SR.InvalidOperation_CannotSetStreamSizeCannotWrite); 414 ex.SetErrorCode(__HResults.E_ILLEGAL_METHOD_CALL); 415 throw ex; 416 } 417 418 Int64 val = unchecked((Int64)value); 419 420 Debug.Assert(str != null); 421 Debug.Assert(str.CanSeek, "The underlying str is expected to support Seek, but it does not."); 422 Debug.Assert(0 <= val, "Unexpected val=" + val + "."); 423 424 str.SetLength(val); 425 } 426 } 427 428 #endregion IRandomAccessStream public interface: Not cloning related 429 430 431 #region IRandomAccessStream public interface: Cloning related 432 433 // We do not want to support the cloning-related operation for now. 434 // They appear to mainly target corner-case scenarios in Windows itself, 435 // and are (mainly) a historical artefact of abandoned early designs 436 // for IRandonAccessStream. 437 // Cloning can be added in future, however, it would be quite complex 438 // to support it correctly for generic streams. 439 ThrowCloningNotSupported(String methodName)440 private static void ThrowCloningNotSupported(String methodName) 441 { 442 NotSupportedException nse = new NotSupportedException(SR.Format(SR.NotSupported_CloningNotSupported, methodName)); 443 nse.SetErrorCode(__HResults.E_NOTIMPL); 444 throw nse; 445 } 446 447 CloneStream()448 public IRandomAccessStream CloneStream() 449 { 450 ThrowCloningNotSupported("CloneStream"); 451 return null; 452 } 453 454 GetInputStreamAt(UInt64 position)455 public IInputStream GetInputStreamAt(UInt64 position) 456 { 457 ThrowCloningNotSupported("GetInputStreamAt"); 458 return null; 459 } 460 461 GetOutputStreamAt(UInt64 position)462 public IOutputStream GetOutputStreamAt(UInt64 position) 463 { 464 ThrowCloningNotSupported("GetOutputStreamAt"); 465 return null; 466 } 467 #endregion IRandomAccessStream public interface: Cloning related 468 469 #endregion IRandomAccessStream public interface 470 471 } // class NetFxToWinRtStreamAdapter 472 } // namespace 473 474 // NetFxToWinRtStreamAdapter.cs 475