sources/Google.Solutions.Mvvm/Interop/ComStream.cs (141 lines of code) (raw):

// // Copyright 2025 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; namespace Google.Solutions.Mvvm.Interop { /// <summary> /// Wraps a managed stream as a COM IStream. /// </summary> internal sealed class ComStream : IStream, IDisposable { private readonly Stream stream; public ComStream(Stream stream) { this.stream = stream; } internal long? SpeculatedPosition { get; private set; } = null; /// <summary> /// Verify and retire a speculation that a previous seek was /// unnecessary. /// </summary> private void RetireSpeculatedSeek() { if (this.SpeculatedPosition != null && this.SpeculatedPosition != this.stream.Position) { throw new NotSupportedException( "The stream does not support seeking"); } this.SpeculatedPosition = null; } //-------------------------------------------------------------------- // IStream. //-------------------------------------------------------------------- /// <summary> /// Reads a specified number of bytes from the stream object into memory /// starting at the current seek pointer. /// </summary> void IStream.Read(byte[] buffer, int count, IntPtr bytesReadPtr) { RetireSpeculatedSeek(); var bytesRead = this.stream.Read(buffer, 0, (int)count); if (bytesReadPtr != IntPtr.Zero) { Marshal.WriteInt32(bytesReadPtr, bytesRead); } } /// <summary> /// Writes a specified number of bytes into the stream object starting /// at the current seek pointer. /// </summary> void IStream.Write(byte[] buffer, int count, IntPtr bytesWrittenPtr) { RetireSpeculatedSeek(); this.stream.Write(buffer, 0, count); if (bytesWrittenPtr != IntPtr.Zero) { // // It's safe to assume that we wrote the entire buffer. // Marshal.WriteInt32(bytesWrittenPtr, count); } } /// <summary> /// Changes the seek pointer to a new location relative to the /// beginning of the stream, to the end of the stream, or to the /// current seek pointer. /// </summary> void IStream.Seek(long offset, int origin, IntPtr newPositionPtr) { var seekOrigin = origin switch { STREAM_SEEK_SET => SeekOrigin.Begin, STREAM_SEEK_CUR => SeekOrigin.Current, STREAM_SEEK_END => SeekOrigin.End, _ => throw new ArgumentOutOfRangeException(nameof(origin)) }; long position; if (this.stream.CanSeek) { position = this.stream.Seek(offset, seekOrigin); } else { // // Assume we're at the right position already. // // - If this is a NOP-seek (like Current + 0 offset), // a subsequent Read or Write will succeed. // // - If this is a Reset-seek performed at the end, // we won't see any subsequent Read or Write, so // we're okay too. // // This is sufficient to satisfy a client that merely // uses seeking to ensure the stream is set to the // beginning before starting to read. // this.SpeculatedPosition = position = seekOrigin switch { SeekOrigin.Begin => offset, SeekOrigin.Current => this.stream.Position + offset, SeekOrigin.End => this.stream.Length + offset, _ => throw new ArgumentOutOfRangeException(nameof(origin)) }; } if (newPositionPtr != IntPtr.Zero) { Marshal.WriteInt64(newPositionPtr, position); } } /// <summary> /// Changes the size of the stream object. /// </summary> void IStream.SetSize(long newSize) { this.stream.SetLength(newSize); } /// <summary> /// Retrieves the STATSTG structure for this stream. /// </summary> void IStream.Stat( out System.Runtime.InteropServices.ComTypes.STATSTG streamStats, int grfStatFlag) { streamStats = new System.Runtime.InteropServices.ComTypes.STATSTG { type = STGTY_STREAM, cbSize = this.stream.Length, grfMode = 0 }; if (this.stream.CanRead && this.stream.CanWrite) { streamStats.grfMode |= STGM_READWRITE; } else if (this.stream.CanRead) { streamStats.grfMode |= STGM_READ; } else if (this.stream.CanWrite) { streamStats.grfMode |= STGM_WRITE; } else { // // A stream that is neither readable nor writable is a closed stream. // throw new IOException("Stream is closed"); } } /// <summary> /// Copies a specified number of bytes from the current seek pointer /// in the stream to the current seek pointer in another stream. /// </summary> void IStream.CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { throw new NotSupportedException(); } /// <summary> /// Discards all changes that have been made to a transacted stream /// since the last Commit(Int32) call. /// </summary> void IStream.Revert() { throw new NotSupportedException(); } /// <summary> /// Restricts access to a specified range of bytes in the stream. /// </summary> void IStream.LockRegion(long libOffset, long cb, int dwLockType) { throw new NotSupportedException(); } /// <summary> /// Removes the access restriction on a range of bytes /// </summary> void IStream.UnlockRegion(long libOffset, long cb, int dwLockType) { throw new NotSupportedException(); } /// <summary> /// Ensures that any changes made to a stream object that is open in /// transacted mode are reflected in the parent storage. /// </summary> void IStream.Commit(int grfCommitFlags) { throw new NotSupportedException(); } /// <summary> /// Creates a new stream object with its own seek pointer that references /// the same bytes as the original stream. /// </summary> void IStream.Clone(out IStream? copy) { copy = null; throw new NotSupportedException(); } //-------------------------------------------------------------------- // IDisposable. //-------------------------------------------------------------------- public void Dispose() { this.stream.Dispose(); } //-------------------------------------------------------------------- // P/Invoke constants. //-------------------------------------------------------------------- internal const int STREAM_SEEK_SET = 0; internal const int STREAM_SEEK_CUR = 1; internal const int STREAM_SEEK_END = 2; internal const int STGTY_STREAM = 2; internal const int STGM_READ = 0; internal const int STGM_WRITE = 1; internal const int STGM_READWRITE = 2; } }