// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices; namespace Roslyn.Utilities { /// /// A COM IStream implementation over memory. Supports just enough for DiaSymReader's PDB writing. /// Also tuned for performance: /// 1. SetSize (and Seek beyond the length) is very fast and doesn't re-allocate the underlying memory. /// 2. Read and Write are optimized to avoid copying (see ) /// 3. Allocates in chunks instead of a contiguous buffer to avoid re-alloc and copy costs when growing. /// internal class ComMemoryStream : IUnsafeComStream { private const int ChunkSize = 32768; private readonly List _chunks = new List(); private int _position; private int _length; public void CopyTo(Stream stream) { // If the target stream allows seeking set its length upfront. // When writing to a large file, it helps to give a hint to the OS how big the file is going to be. if (stream.CanSeek) { stream.SetLength(stream.Position + _length); } int chunkIndex = 0; for (int remainingBytes = _length; remainingBytes > 0;) { int bytesToCopy = Math.Min(ChunkSize, remainingBytes); if (chunkIndex < _chunks.Count) { stream.Write(_chunks[chunkIndex++], 0, bytesToCopy); } else { // Fill remaining space with zero bytes for (int i = 0; i < bytesToCopy; i++) { stream.WriteByte(0); } } remainingBytes -= bytesToCopy; } } private unsafe static void ZeroMemory(IntPtr dest, int count) { var p = (byte*)dest; while (count-- > 0) { *p++ = 0; } } unsafe void IUnsafeComStream.Read(IntPtr pv, int cb, IntPtr pcbRead) { int chunkIndex = _position / ChunkSize; int chunkOffset = _position % ChunkSize; int destinationIndex = 0; int bytesRead = 0; while (true) { int bytesToCopy = Math.Min(_length - _position, Math.Min(cb, ChunkSize - chunkOffset)); if (bytesToCopy == 0) { break; } if (chunkIndex < _chunks.Count) { Marshal.Copy(_chunks[chunkIndex], chunkOffset, pv + destinationIndex, bytesToCopy); } else { ZeroMemory(pv + destinationIndex, bytesToCopy); } bytesRead += bytesToCopy; _position += bytesToCopy; cb -= bytesToCopy; destinationIndex += bytesToCopy; chunkIndex++; chunkOffset = 0; } if (pcbRead != IntPtr.Zero) { *(int*)pcbRead = bytesRead; } } private int SetPosition(int newPos) { if (newPos < 0) { newPos = 0; } _position = newPos; if (newPos > _length) { _length = newPos; } return newPos; } unsafe void IUnsafeComStream.Seek(long dlibMove, int origin, IntPtr plibNewPosition) { int newPosition; switch (origin) { case 0: // STREAM_SEEK_SET newPosition = SetPosition((int)dlibMove); break; case 1: // STREAM_SEEK_CUR newPosition = SetPosition(_position + (int)dlibMove); break; case 2: // STREAM_SEEK_END newPosition = SetPosition(_length + (int)dlibMove); break; default: throw new ArgumentException($"{nameof(origin)} ({origin}) is invalid.", nameof(origin)); } if (plibNewPosition != IntPtr.Zero) { *(long*)plibNewPosition = newPosition; } } void IUnsafeComStream.SetSize(long libNewSize) { _length = (int)libNewSize; } void IUnsafeComStream.Stat(out STATSTG pstatstg, int grfStatFlag) { pstatstg = new STATSTG() { cbSize = _length }; } unsafe void IUnsafeComStream.Write(IntPtr pv, int cb, IntPtr pcbWritten) { int chunkIndex = _position / ChunkSize; int chunkOffset = _position % ChunkSize; int bytesWritten = 0; while (true) { int bytesToCopy = Math.Min(cb, ChunkSize - chunkOffset); if (bytesToCopy == 0) { break; } while (chunkIndex >= _chunks.Count) { _chunks.Add(new byte[ChunkSize]); } Marshal.Copy(pv + bytesWritten, _chunks[chunkIndex], chunkOffset, bytesToCopy); bytesWritten += bytesToCopy; cb -= bytesToCopy; chunkIndex++; chunkOffset = 0; } SetPosition(_position + bytesWritten); if (pcbWritten != IntPtr.Zero) { *(int*)pcbWritten = bytesWritten; } } void IUnsafeComStream.Commit(int grfCommitFlags) { } void IUnsafeComStream.Clone(out IStream ppstm) { throw new NotSupportedException(); } void IUnsafeComStream.CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) { throw new NotSupportedException(); } void IUnsafeComStream.LockRegion(long libOffset, long cb, int lockType) { throw new NotSupportedException(); } void IUnsafeComStream.Revert() { throw new NotSupportedException(); } void IUnsafeComStream.UnlockRegion(long libOffset, long cb, int lockType) { throw new NotSupportedException(); } } }