How to use a System.IO.Stream .NET object as a COM IStream interface
See the question and my original answer on StackOverflowYou can use IStream which is a fairly well-known Windows (COM) type and implement it with C# (both ways from native to .NET and from .NET to native), for example like this:
public sealed class ManagedIStream : System.Runtime.InteropServices.ComTypes.IStream
{
private readonly Stream _stream;
public ManagedIStream(Stream stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
public void Read(byte[] pv, int cb, IntPtr pcbRead)
{
if (pv == null)
throw new ArgumentNullException(nameof(pv));
var read = _stream.Read(pv, 0, cb);
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, read);
}
}
public void Write(byte[] pv, int cb, IntPtr pcbWritten)
{
if (pv == null)
throw new ArgumentNullException(nameof(pv));
_stream.Write(pv, 0, cb);
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt32(pcbWritten, cb);
}
}
public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
{
var newPos = _stream.Seek(dlibMove, (SeekOrigin)dwOrigin);
if (plibNewPosition != IntPtr.Zero)
{
Marshal.WriteInt64(plibNewPosition, newPos);
}
}
public void SetSize(long libNewSize) => _stream.SetLength(libNewSize);
public void Commit(int grfCommitFlags) => _stream.Flush();
public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
{
pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG
{
type = (int)STGTY.STGTY_STREAM,
cbSize = _stream.Length,
grfMode = 0
};
if (_stream.CanRead && _stream.CanWrite)
{
pstatstg.grfMode |= (int)STGM.STGM_READWRITE;
return;
}
if (_stream.CanRead)
{
pstatstg.grfMode |= (int)STGM.STGM_READ;
return;
}
if (_stream.CanWrite)
{
pstatstg.grfMode |= (int)STGM.STGM_WRITE;
return;
}
throw new IOException();
}
public void CopyTo(System.Runtime.InteropServices.ComTypes.IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
{
if (pstm == null)
throw new ArgumentNullException(nameof(pstm));
long count;
using (var stream = new StreamOnIStream(pstm))
{
count = CopyTo(_stream, stream, cb);
}
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt64(pcbRead, count);
}
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt64(pcbWritten, count);
}
}
private static long CopyTo(Stream input, Stream output, long count = long.MaxValue, int bufferSize = 0x14000)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (output == null)
throw new ArgumentNullException(nameof(output));
if (count <= 0)
throw new ArgumentException(null, nameof(count));
if (bufferSize <= 0)
throw new ArgumentException(null, nameof(bufferSize));
if (count < bufferSize)
{
bufferSize = (int)count;
}
var bytes = new byte[bufferSize];
var total = 0;
do
{
var max = (int)Math.Min(count - total, bytes.Length);
var read = input.Read(bytes, 0, max);
if (read == 0)
break;
output.Write(bytes, 0, read);
total += read;
if (total == count)
break;
}
while (true);
return total;
}
public void Revert() => throw new NotSupportedException();
public void LockRegion(long libOffset, long cb, int dwLockType) => throw new NotSupportedException();
public void UnlockRegion(long libOffset, long cb, int dwLockType) => throw new NotSupportedException();
public void Clone(out System.Runtime.InteropServices.ComTypes.IStream ppstm) => throw new NotSupportedException();
}
public class StreamOnIStream : Stream
{
private const int STATFLAG_NONAME = 1;
private System.Runtime.InteropServices.ComTypes.IStream _stream;
private IntPtr _ptr;
private long _position;
public StreamOnIStream(System.Runtime.InteropServices.ComTypes.IStream stream)
{
_stream = stream;
_ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf<long>()); // works for 32b & 64b
CanRead = true;
CanSeek = true;
CanWrite = true;
}
public System.Runtime.InteropServices.ComTypes.IStream NativeStream => CheckDisposed();
public override bool CanTimeout => false;
public override int ReadTimeout => Timeout.Infinite;
public override int WriteTimeout => Timeout.Infinite;
public override bool CanRead { get; }
public override bool CanSeek { get; }
public override bool CanWrite { get; }
public override long Position { get => _position; set => Seek(value, SeekOrigin.Begin); }
public override long Length { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return stat.cbSize; } }
public DateTimeOffset CreationTime { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return ToDateTimeOffset(stat.ctime); } }
public DateTimeOffset LastWriteTime { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return ToDateTimeOffset(stat.mtime); } }
public DateTimeOffset LastAccessTime { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return ToDateTimeOffset(stat.atime); } }
public Guid Clsid { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return stat.clsid; } }
public string Name { get { CheckDisposed().Stat(out var stat, 0); return stat.pwcsName; } }
public STGM StorageMode { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return (STGM)stat.grfMode; } }
public STGTY StorageType { get { CheckDisposed().Stat(out var stat, STATFLAG_NONAME); return (STGTY)stat.type; } }
private static DateTimeOffset ToDateTimeOffset(System.Runtime.InteropServices.ComTypes.FILETIME fileTime)
{
var ft = (((long)fileTime.dwHighDateTime) << 32) + fileTime.dwLowDateTime;
return DateTimeOffset.FromFileTime(ft);
}
public virtual void Flush(STGC options) => CheckDisposed().Commit((int)options);
public override void Flush() => Flush(STGC.STGC_DEFAULT);
public override int Read(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (count == 0)
return 0;
if (offset == 0)
return Read(buffer, count);
var bytes = new byte[count];
var read = Read(bytes, bytes.Length);
if (read > 0)
{
Array.Copy(bytes, 0, buffer, offset, read);
}
return read;
}
private int Read(byte[] buffer, int count)
{
CheckDisposed().Read(buffer, count, _ptr);
var read = Marshal.ReadInt32(_ptr);
_position += read;
return read;
}
public override void SetLength(long value) => CheckDisposed().SetSize(value);
public override long Seek(long offset, SeekOrigin origin)
{
CheckDisposed().Seek(offset, (int)origin, _ptr);
_position = Marshal.ReadInt64(_ptr);
return _position;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(offset));
if (count == 0)
return;
if (offset == 0)
{
CheckDisposed().Write(buffer, count, _ptr);
}
else
{
var bytes = new byte[count];
Array.Copy(buffer, offset, bytes, 0, count);
CheckDisposed().Write(bytes, bytes.Length, _ptr);
}
var written = Marshal.ReadInt32(_ptr);
_position += written;
}
private System.Runtime.InteropServices.ComTypes.IStream CheckDisposed()
{
var stream = _stream;
if (stream == null)
throw new ObjectDisposedException(nameof(NativeStream));
return stream;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
var stream = Interlocked.Exchange(ref _stream, null);
if (stream != null)
{
try
{
stream.Commit((int)STGC.STGC_DEFAULT);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
{
//+ do nothing
}
#pragma warning restore CA1031 // Do not catch general exception types
}
var ptr = Interlocked.Exchange(ref _ptr, IntPtr.Zero);
if (ptr != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(ptr);
}
}
}
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1712:Do not prefix enum values with type name")]
public enum STGC
{
STGC_DEFAULT = 0x0,
STGC_OVERWRITE = 0x1,
STGC_ONLYIFCURRENT = 0x2,
STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 0x4,
STGC_CONSOLIDATE = 0x8
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1712:Do not prefix enum values with type name")]
[Flags]
public enum STGM
{
STGM_DIRECT = 0x00000000,
STGM_TRANSACTED = 0x00010000,
STGM_SIMPLE = 0x08000000,
STGM_READ = 0x00000000,
STGM_WRITE = 0x00000001,
STGM_READWRITE = 0x00000002,
STGM_SHARE_DENY_NONE = 0x00000040,
STGM_SHARE_DENY_READ = 0x00000030,
STGM_SHARE_DENY_WRITE = 0x00000020,
STGM_SHARE_EXCLUSIVE = 0x00000010,
STGM_PRIORITY = 0x00040000,
STGM_DELETEONRELEASE = 0x04000000,
STGM_NOSCRATCH = 0x00100000,
STGM_CREATE = 0x00001000,
STGM_CONVERT = 0x00020000,
STGM_FAILIFTHERE = 0x00000000,
STGM_NOSNAPSHOT = 0x00200000,
STGM_DIRECT_SWMR = 0x00400000,
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1712:Do not prefix enum values with type name")]
public enum STGTY
{
STGTY_STORAGE = 1,
STGTY_STREAM = 2,
STGTY_LOCKBYTES = 3,
STGTY_PROPERTY = 4,
}