| File: MS\Internal\IO\Packaging\UnsafeIndexingFilterStream.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: The class UnsafeIndexingFilterStream uses an OLE IStream component // passed on a indexing filter's IPersistStream interface to implement // the System.IO.Stream functions necessary for filtering a document. // In other words, it basically implements a seekable read-only stream. // // For a more complete example of an IStream adapter, see Listing 20.2 // in Adam Nathan's ".Net and COM". // using System.IO; using System.Runtime.InteropServices; // For Marshal using System.Windows; // for ExceptionStringTable using MS.Win32; // For NativeMethods using MS.Internal.Interop; // for IStream namespace MS.Internal.IO.Packaging { /// <summary> /// The class UnsafeIndexingFilterStream uses an OLE IStream component /// passed on an indexing filter's IPersistStream interface to implement /// the System.IO.Stream functions necessary for filtering a document. /// In other words, it basically implements a seekable read-only stream. /// </summary> /// <remarks> /// /// This class is used only by the Container filter, since the Xaml filter is not accessible directly /// from unmanaged code and so can use System.IO.Stream natively. /// /// This class does not own the process of closing the underlying stream. However, /// This class does own a reference to a COM object that should be released as a part of the Dispose pattern, /// so that the underlying unmanaged code doesn't keep the stream open indefinitely (or until GC gets to it) /// /// The definition of IStream that is used is MS.Internal.Interop.IStream rather than the standard one /// so as to allow efficient marshaling of arrays with specified offsets in Read. /// </remarks> internal class UnsafeIndexingFilterStream : Stream { //------------------------------------------------------ // // Constructors // //------------------------------------------------------ /// <summary> /// Build a System.IO.Stream implementation around an IStream component. /// </summary> /// <remarks> /// The client code is entirely responsible for the lifespan of the stream, /// and there is no way it can tip us off for when to release it. Therefore, /// its reference count is not incremented. The risk of the client /// releasing the IStream component before we're done with it is no worse than /// that of the client passing a pointer to garbage in the first place, and we /// cannot protect against that either. After all, the client is unmanaged and /// has endless possibilities of trashing the machine if she wishes to. /// </remarks> internal UnsafeIndexingFilterStream(IStream oleStream) { ArgumentNullException.ThrowIfNull(oleStream); _oleStream = oleStream; _disposed = false; } //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ /// <summary> /// Return the bytes requested. /// </summary> /// <param name="buffer">Destination buffer.</param> /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> /// <param name="count">How many bytes requested.</param> /// <returns>How many bytes were written into buffer.</returns> public override unsafe int Read(byte[] buffer, int offset, int count) { ThrowIfStreamDisposed(); // Check arguments. PackagingUtilities.VerifyStreamReadArgs(this, buffer, offset, count); // Reading 0 bytes is a no-op. if (count == 0) return 0; // Prepare location of return value and call the COM object. int bytesRead; IntPtr pBytesRead = new IntPtr(&bytesRead); // Prepare to restore position in case the read fails. long positionBeforeReadAttempt = this.Position; try { // Pin the array wrt GC while using an address in it. fixed (byte *bufferPointer = &buffer[offset]) { _oleStream.Read(new IntPtr(bufferPointer), count, pBytesRead); } } catch (COMException comException) { this.Position = positionBeforeReadAttempt; throw new IOException("Read", comException); } catch (IOException ioException) { this.Position = positionBeforeReadAttempt; throw new IOException("Read", ioException); } return bytesRead; } /// <summary> /// Seek -unmanaged streams do not allow seeking beyond the end of the stream /// and since we rely on the underlying stream to validate and return the seek /// results, unlike managed streams where seeking beyond the end of the stream /// is allowed we will get an exception. /// </summary> /// <param name="offset">Offset in byte.</param> /// <param name="origin">Offset origin (start, current, or end).</param> public override unsafe long Seek(long offset, SeekOrigin origin) { ThrowIfStreamDisposed(); long position = 0; // The address of 'position' can be used without pinning the object, because it // is a value and is therefore allocated on the stack rather than the heap. IntPtr positionAddress = new IntPtr(&position); // The enum values of SeekOrigin match the STREAM_SEEK_* values. This // convention is as good as carved in stone, so there's no need for a switch here. _oleStream.Seek(offset, (int)origin, positionAddress); return position; } /// <summary> /// SetLength /// </summary> /// <exception cref="NotSupportedException">not supported</exception> /// <remarks> /// Not supported. No indexing filter should require it. /// </remarks> public override void SetLength(long newLength) { ThrowIfStreamDisposed(); throw new NotSupportedException(SR.StreamDoesNotSupportWrite); } /// <summary> /// Write /// </summary> /// <exception cref="NotSupportedException">not supported</exception> /// <remarks> /// Not supported. No indexing filter should require it. /// </remarks> public override void Write(byte[] buf, int offset, int count) { ThrowIfStreamDisposed(); throw new NotSupportedException(SR.StreamDoesNotSupportWrite); } /// <summary> /// Flush /// </summary> public override void Flush() { ThrowIfStreamDisposed(); //This stream is always readonly, and calling this method is a no-op //No IndexingFilter should require this method. } //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ /// <summary> /// Is stream readable? /// </summary> /// <remarks> /// We always return true, because there's no way of checking whether the caller /// has closed the stream. /// </remarks> public override bool CanRead { get { return !_disposed; } } /// <summary> /// Is stream seekable? /// </summary> public override bool CanSeek { get { // This information is not available from the underlying stream. // So one assumption has to be made. True is the most common for indexable streams. return !_disposed; } } /// <summary> /// Is stream writable? /// </summary> public override bool CanWrite { get { return false; } } /// <summary> /// Logical byte position in this stream /// </summary> public override long Position { get { ThrowIfStreamDisposed(); return Seek(0, SeekOrigin.Current); } set { ThrowIfStreamDisposed(); if (value < 0) throw new ArgumentException(SR.CannotSetNegativePosition); Seek(value, SeekOrigin.Begin); } } /// <summary> /// Length. /// </summary> public override long Length { get { ThrowIfStreamDisposed(); // Retrieve stream stats. STATFLAG_NONAME means don't return the stream name. System.Runtime.InteropServices.ComTypes.STATSTG statstg; _oleStream.Stat(out statstg, NativeMethods.STATFLAG_NONAME); return statstg.cbSize; } } //------------------------------------------------------ // // Protected methods. // //------------------------------------------------------ /// <summary> /// <para> /// Although UnsafeIndexingFilterStream does not close the underlying stream, it is responsible for releasing /// the ComObject it holds, so that unmanaged code can properly close the stream. /// </para> <para> /// This method gets invoked as part of the base class's Dispose() or Close() implementation. /// </para> /// </summary> protected override void Dispose(bool disposing) { try { if (disposing) { if (_oleStream != null) { MS.Win32.UnsafeNativeMethods.SafeReleaseComObject(_oleStream); } } } finally { // Calls to Dispose(bool) are expected to bubble up through the class hierarchy. _oleStream = null; _disposed = true; base.Dispose(disposing); } } //------------------------------------------------------ // // Private methods. // //------------------------------------------------------ private void ThrowIfStreamDisposed() { if (_disposed) throw new ObjectDisposedException(null, SR.StreamObjectDisposed); } //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ private IStream _oleStream; // Underlying COM component. private bool _disposed; } }