wiki:Windows/CSharpIMemoryStreamUnmanagedComInterop

IMemoryStream for C# and unmanaged COM Interop Streams

Summary

A convenience class to aid in transferring data between managed and unmanaged objects via an in-memory stream. I developed it to work with MimeSniffer so I could transfer Strings into a MimeSniffer object and export a MimeBody object to a stream. My pop3ImageGrabber makes use of !IMemoryStream.

IMemoryStream inherits from MemoryStream and implements IStream and IDispatch.

27th July 2005.

Introduction

In a recent project I needed to do two things:

  1. Pass an LPUNKNOWN pointer to a Stream from my managed C# code into a COM object method
  2. Allow an unmanaged COM object to read and write my managed C# Stream via an IStream

I have implemented a MemoryStream to help pass data from a static code library to the unmanaged COM object, and also from the unmanaged COM object to a managed Bitmap.

I was working with Randolf Duke's  MimeSniffer component to extract image attachments, create thumbnails, superimpose a copyright message, and save to disk.

I had to extend MemoryStream to implement the IStream and IDispatch interfaces. It is an incomplete implementation of IStream, so based on your own needs you may need to add code to some of the methods and add helper methods too.

Prerequisites

Microsoft .net Framework 1.1 or later.

Source Code

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace net.tjworld.utils {

 // For framework 1.1 define the COM IStream interface (framework 2.x has it built in)
 // Thanks to Oliver Sturm for this
 [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 public interface IStream {
  void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] pv, int cb, IntPtr pcbRead);
  void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] byte[] pv, int cb, IntPtr pcbWritten);
  void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition);
  void SetSize(long libNewSize);
  void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten);
  void Commit(int grfCommitFlags);
  void Revert();
  void LockRegion(long libOffset, long cb, int dwLockType);
  void UnlockRegion(long libOffset, long cb, int dwLockType);
  void Stat(out STATSTG pstatstg, int grfStatFlag);
  void Clone(out IStream ppstm);
 }

 /// <summary>
 /// COM IStream wrapper for a MemoryStream.
 /// Thanks to Willy Denoyette for the if(System.IntPr != IntPtr.Zero) test for a NULL parameter via COM
 /// CLR will make the class implement the IDispatch COM interface
 /// so COM objects can make calls to IMemoryStream methods
 /// </summary>
 [ClassInterface(ClassInterfaceType.AutoDispatch)] 
 public class IMemoryStream : MemoryStream, IStream {
  public IMemoryStream() : base() { 

  // convenience method for writing Strings to the stream
  public void Write(string s) {
  System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
  byte[] pv = encoding.GetBytes(s);
  Write(pv, 0, pv.GetLength(0));
  }

  // Implementation of the IStream interface
  public void Clone(out IStream ppstm) {
   ppstm = null;
  }

  public void Read(byte[] pv, int cb, System.IntPtr pcbRead) {
   long bytesRead = Read(pv, 0, cb);
   if(pcbRead != IntPtr.Zero) Marshal.WriteInt64(pcbRead, bytesRead);
  }

  public void Write(byte[] pv, int cb, System.IntPtr pcbWritten) {
   Write(pv, 0, cb); 
   if(pcbWritten != IntPtr.Zero) Marshal.WriteInt64(pcbWritten, (Int64)cb);
  }

  public void Seek(long dlibMove, int dwOrigin, System.IntPtr plibNewPosition) {
   long pos = base.Seek(dlibMove, (SeekOrigin)dwOrigin);
   if(plibNewPosition != IntPtr.Zero) Marshal.WriteInt64(plibNewPosition, pos);
  }

  public void SetSize(long libNewSize) { }

  public void CopyTo(IStream pstm, long cb, System.IntPtr pcbRead, System.IntPtr pcbWritten) { }

  public void Commit(int grfCommitFlags) { }

  public void LockRegion(long libOffset, long cb, int dwLockType) { }

  public void Revert() { }

  public void UnlockRegion(long libOffset, long cb, int dwLockType) { }

  public void Stat(out STATSTG pstatstg, int grfStatFlag) {
   pstatstg = new STATSTG();
  }
 }
}

Using IMemoryStream

Writing a string into the stream and then having an unmanaged COM object read the stream. Don't forget to seek to the beginning of the stream before reading else you'll get nothing.

string result = email.body;
IMemoryStream ms = new IMemoryStream();
ms.Write(result);
ms.Seek(0, SeekOrigin.Begin); // return to start of stream ready for reading
mime.Load(ms); // transfer into a MimeSniffer object

Writing to the stream from an unmanaged COM object that takes an IStream pointer then reading it into a managed Bitmap object.

ms = new IMemoryStream();
attachment.Export(ms); // MimeSniffer.MimeBody.Export(Stream)
image = new Bitmap((Stream)ms);

Credits

Randolf Duke's  mimeSniffer component
Oliver Sturm & Willy Denoyette in the microsoft.public.dotnet.framework.interop newsgroups