|
// 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.
//
// 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 unsafe override 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 unsafe override 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;
}
}
|