1
votes

Does anyone have any C# code to accept Drag and Drop from Outlook to a Winforms application that works under .Net Framework 4.5 or later please?

I have some code that's been in use for about 12 years now, which includes the ability to drag and drop items, including emails from Outlook.

The code has been running perfectly when compiled with the Target Framework set to .Net Framework 4, however some new features I've been working on require .Net Framework 4.5 or later. This however prevents the Drag and Drop from Outlook working.

The code is quite convoluted, it does a ton of stuff unrelated to the issue to determine where the file goes, and who it shows up for etc, so I won't include all that, but the point at which it breaks is nice and simple...

public string ImportEmail(DragEventArgs e)
    {
        string strResult = string.Empty;
        var dataObject = new OutlookDataObject(e.Data);
        var filenames = (string[]) dataObject.GetData("FileGroupDescriptor");

Under .NET 4, the last line above returns the filename(s) as expected.

Under .NET 4.5 or later, the last line above returns null.

I tested it under .NET 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2 and 4.7.2 (all the versions I have installed), it doesn't work on any of them.

I've been trawling around for a couple of days trying out all the Drag and Drop code I could find, and none of it seems to work under .Net 4.5 or later.

Below is the complete code for a test form I did, using code I dug up from here and elsewhere. This code works perfectly under .Net Framework 4, whether you drag and drop a file or an email from Outlook, but neither works under .Net Framework 4.5 or later (tested all the same versions again).

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Reflection;
using System.Windows.Forms;

namespace OutlookDragNDropTest
{
    public partial class OutlookDragNDropTest : Form
    {
        public OutlookDragNDropTest()
        {
            InitializeComponent();
        }

        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
            //    or this tells us if it is an Outlook attachment drop
            else if (e.Data.GetDataPresent("FileGroupDescriptor"))
            {
                e.Effect = DragDropEffects.Copy;
            }
            //    or none of the above
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void Form1_DragDrop(object sender, DragEventArgs e)
        {
            try
            {
                //wrap standard IDataObject in OutlookDataObject
                OutlookDataObject dataObject = new OutlookDataObject(e.Data);

                //get the names and data streams of the files dropped
                string[] filenames = (string[])dataObject.GetData("FileGroupDescriptor");
                MemoryStream[] filestreams = (MemoryStream[])dataObject.GetData("FileContents");

                string tempPath = Path.GetTempPath();

                for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
                {
                    //use the fileindex to get the name and data stream
                    string filename = tempPath + filenames[fileIndex];
                    MemoryStream filestream = filestreams[fileIndex];

                    //save the file stream using its name to the application path
                    FileStream outputStream = File.Create(filename);
                    filestream.WriteTo(outputStream);
                    outputStream.Close();
                    MessageBox.Show("Output to " + filename);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error : " + ex.ToString());
            }
        }
    }
}

(Ok, seems that code was too long for one post, the OutlookDataObject class will be in a reply below)...

The only other thing is the form itself, which is just a blank Winforms form, with the following Properties set:-

AllowDrop : True

DragEnter event : Form1_DragEnter

DragDrop event : Form1_DragDrop

If it's relevant, I'm using Visual Studio 2017 Professional, and running on Windows 10, although the Drag and Drop not working has also been reported by clients running Windows 7.

2

2 Answers

4
votes

Solved it!

The issue turned out to be that .Net Framework 4 and lower use 32-bit pointers, and .Net Framework 4.5 and above use 64-bit pointers.

The ancient code I had, and the example code I provided, were both written on the assumption that pointers would only be 32-bit.

Changing the following two lines in the OutlookDataObject class (and making a similar change in my code) fixed it:-

From:-

IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));

fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));

To:-

IntPtr fileDescriptorPointer = (IntPtr)((long)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));

fileDescriptorPointer = (IntPtr)((long)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));

Not sure why my Visual Studio didn't throw a wobbly when it hit those lines, but oh well.

With the above change, the example code I provided handles drag and drop of files and Outlook emails, both single items and multiple items at once

0
votes

The OutlookDataObject class for the code above, as it was too long for one post

public class OutlookDataObject : System.Windows.Forms.IDataObject
{
    #region NativeMethods

    private class NativeMethods
    {
        [DllImport("kernel32.dll")]
        static extern IntPtr GlobalLock(IntPtr hMem);

        [DllImport("ole32.dll", PreserveSig = false)]
        public static extern ILockBytes CreateILockBytesOnHGlobal(IntPtr hGlobal, bool fDeleteOnRelease);

        [DllImport("OLE32.DLL", CharSet = CharSet.Auto, PreserveSig = false)]
        public static extern IntPtr GetHGlobalFromILockBytes(ILockBytes pLockBytes);

        [DllImport("OLE32.DLL", CharSet = CharSet.Unicode, PreserveSig = false)]
        public static extern IStorage StgCreateDocfileOnILockBytes(ILockBytes plkbyt, uint grfMode, uint reserved);

        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000B-0000-0000-C000-000000000046")]
        public interface IStorage
        {
            [return: MarshalAs(UnmanagedType.Interface)]
            IStream CreateStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
            [return: MarshalAs(UnmanagedType.Interface)]
            IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
            [return: MarshalAs(UnmanagedType.Interface)]
            IStorage CreateStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
            [return: MarshalAs(UnmanagedType.Interface)]
            IStorage OpenStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr pstgPriority, [In, MarshalAs(UnmanagedType.U4)] int grfMode, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.U4)] int reserved);
            void CopyTo(int ciidExclude, [In, MarshalAs(UnmanagedType.LPArray)] Guid[] pIIDExclude, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest);
            void MoveElementTo([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.Interface)] IStorage stgDest, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName, [In, MarshalAs(UnmanagedType.U4)] int grfFlags);
            void Commit(int grfCommitFlags);
            void Revert();
            void EnumElements([In, MarshalAs(UnmanagedType.U4)] int reserved1, IntPtr reserved2, [In, MarshalAs(UnmanagedType.U4)] int reserved3, [MarshalAs(UnmanagedType.Interface)] out object ppVal);
            void DestroyElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsName);
            void RenameElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsOldName, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName);
            void SetElementTimes([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In] System.Runtime.InteropServices.ComTypes.FILETIME pctime, [In] System.Runtime.InteropServices.ComTypes.FILETIME patime, [In] System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
            void SetClass([In] ref Guid clsid);
            void SetStateBits(int grfStateBits, int grfMask);
            void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pStatStg, int grfStatFlag);
        }

        [ComImport, Guid("0000000A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface ILockBytes
        {
            void ReadAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbRead);
            void WriteAt([In, MarshalAs(UnmanagedType.U8)] long ulOffset, IntPtr pv, [In, MarshalAs(UnmanagedType.U4)] int cb, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pcbWritten);
            void Flush();
            void SetSize([In, MarshalAs(UnmanagedType.U8)] long cb);
            void LockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
            void UnlockRegion([In, MarshalAs(UnmanagedType.U8)] long libOffset, [In, MarshalAs(UnmanagedType.U8)] long cb, [In, MarshalAs(UnmanagedType.U4)] int dwLockType);
            void Stat([Out]out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, [In, MarshalAs(UnmanagedType.U4)] int grfStatFlag);
        }

        [StructLayout(LayoutKind.Sequential)]
        public sealed class POINTL
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public sealed class SIZEL
        {
            public int cx;
            public int cy;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public sealed class FILEGROUPDESCRIPTORA
        {
            public uint cItems;
            public FILEDESCRIPTORA[] fgd;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public sealed class FILEDESCRIPTORA
        {
            public uint dwFlags;
            public Guid clsid;
            public SIZEL sizel;
            public POINTL pointl;
            public uint dwFileAttributes;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            public uint nFileSizeHigh;
            public uint nFileSizeLow;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string cFileName;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public sealed class FILEGROUPDESCRIPTORW
        {
            public uint cItems;
            public FILEDESCRIPTORW[] fgd;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public sealed class FILEDESCRIPTORW
        {
            public uint dwFlags;
            public Guid clsid;
            public SIZEL sizel;
            public POINTL pointl;
            public uint dwFileAttributes;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            public uint nFileSizeHigh;
            public uint nFileSizeLow;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string cFileName;
        }
    }

    #endregion

    #region Property(s)

    /// <summary>
    /// Holds the <see cref="System.Windows.Forms.IDataObject"/> that this class is wrapping
    /// </summary>
    private System.Windows.Forms.IDataObject underlyingDataObject;



    private System.Runtime.InteropServices.ComTypes.IDataObject comUnderlyingDataObject;



    private System.Windows.Forms.IDataObject oleUnderlyingDataObject;
    private MethodInfo getDataFromHGLOBLALMethod;
    #endregion

    #region Constructor(s)

    public OutlookDataObject(System.Windows.Forms.IDataObject underlyingDataObject)
    {

        this.underlyingDataObject = underlyingDataObject;
        this.comUnderlyingDataObject = (System.Runtime.InteropServices.ComTypes.IDataObject)this.underlyingDataObject;

        FieldInfo innerDataField = this.underlyingDataObject.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
        this.oleUnderlyingDataObject = (System.Windows.Forms.IDataObject)innerDataField.GetValue(this.underlyingDataObject);
        this.getDataFromHGLOBLALMethod = this.oleUnderlyingDataObject.GetType().GetMethod("GetDataFromHGLOBLAL", BindingFlags.NonPublic | BindingFlags.Instance);
    }

    #endregion

    #region IDataObject Members

    public object GetData(Type format)
    {
        return this.GetData(format.FullName);
    }


    public object GetData(string format)
    {
        return this.GetData(format, true);
    }

    public object GetData(string format, bool autoConvert)
    {
        switch (format)
        {
            case "FileGroupDescriptor":
                IntPtr fileGroupDescriptorAPointer = IntPtr.Zero;
                try
                {
                    //use the underlying IDataObject to get the FileGroupDescriptor as a MemoryStream
                    MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData("FileGroupDescriptor", autoConvert);
                    byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
                    fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
                    fileGroupDescriptorStream.Close();

                    //copy the file group descriptor into unmanaged memory 
                    fileGroupDescriptorAPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
                    Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorAPointer, fileGroupDescriptorBytes.Length);

                    //marshal the unmanaged memory to to FILEGROUPDESCRIPTORA struct
                    object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorAPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORA));
                    NativeMethods.FILEGROUPDESCRIPTORA fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORA)fileGroupDescriptorObject;

                    //create a new array to store file names in of the number of items in the file group descriptor
                    string[] fileNames = new string[fileGroupDescriptor.cItems];

                    //get the pointer to the first file descriptor
                    IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));

                    //loop for the number of files acording to the file group descriptor
                    for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
                    {

                        //marshal the pointer top the file descriptor as a FILEDESCRIPTORA struct and get the file name
                        NativeMethods.FILEDESCRIPTORA fileDescriptor = (NativeMethods.FILEDESCRIPTORA)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORA));
                        fileNames[fileDescriptorIndex] = fileDescriptor.cFileName;

                        //move the file descriptor pointer to the next file descriptor
                        fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
                    }

                    //return the array of filenames
                    return fileNames;
                }
                finally
                {
                    //free unmanaged memory pointer
                    Marshal.FreeHGlobal(fileGroupDescriptorAPointer);
                }

            case "FileGroupDescriptorW":
                //override the default handling of FileGroupDescriptorW which returns a
                //MemoryStream and instead return a string array of file names
                IntPtr fileGroupDescriptorWPointer = IntPtr.Zero;
                try
                {
                    //use the underlying IDataObject to get the FileGroupDescriptorW as a MemoryStream
                    MemoryStream fileGroupDescriptorStream = (MemoryStream)this.underlyingDataObject.GetData("FileGroupDescriptorW");
                    byte[] fileGroupDescriptorBytes = new byte[fileGroupDescriptorStream.Length];
                    fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length);
                    fileGroupDescriptorStream.Close();

                    //copy the file group descriptor into unmanaged memory
                    fileGroupDescriptorWPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length);
                    Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorWPointer, fileGroupDescriptorBytes.Length);

                    //marshal the unmanaged memory to to FILEGROUPDESCRIPTORW struct
                    object fileGroupDescriptorObject = Marshal.PtrToStructure(fileGroupDescriptorWPointer, typeof(NativeMethods.FILEGROUPDESCRIPTORW));
                    NativeMethods.FILEGROUPDESCRIPTORW fileGroupDescriptor = (NativeMethods.FILEGROUPDESCRIPTORW)fileGroupDescriptorObject;

                    //create a new array to store file names in of the number of items in the file group descriptor
                    string[] fileNames = new string[fileGroupDescriptor.cItems];

                    //get the pointer to the first file descriptor
                    //get the pointer to the first file descriptor
                    IntPtr fileDescriptorPointer = (IntPtr)((int)fileGroupDescriptorWPointer + Marshal.SizeOf(fileGroupDescriptor.cItems));


                    //loop for the number of files acording to the file group descriptor
                    for (int fileDescriptorIndex = 0; fileDescriptorIndex < fileGroupDescriptor.cItems; fileDescriptorIndex++)
                    {
                        //marshal the pointer top the file descriptor as a FILEDESCRIPTORW struct and get the file name
                        NativeMethods.FILEDESCRIPTORW fileDescriptor = (NativeMethods.FILEDESCRIPTORW)Marshal.PtrToStructure(fileDescriptorPointer, typeof(NativeMethods.FILEDESCRIPTORW));
                        fileNames[fileDescriptorIndex] = fileDescriptor.cFileName;

                        //move the file descriptor pointer to the next file descriptor
                        fileDescriptorPointer = (IntPtr)((int)fileDescriptorPointer + Marshal.SizeOf(fileDescriptor));
                    }

                    //return the array of filenames
                    return fileNames;
                }
                finally
                {
                    //free unmanaged memory pointer
                    Marshal.FreeHGlobal(fileGroupDescriptorWPointer);
                }

            case "FileContents":
                //override the default handling of FileContents which returns the
                //contents of the first file as a memory stream and instead return
                //a array of MemoryStreams containing the data to each file dropped

                //get the array of filenames which lets us know how many file contents exist
                string[] fileContentNames = (string[])this.GetData("FileGroupDescriptor");

                //create a MemoryStream array to store the file contents
                MemoryStream[] fileContents = new MemoryStream[fileContentNames.Length];

                //loop for the number of files acording to the file names
                for (int fileIndex = 0; fileIndex < fileContentNames.Length; fileIndex++)
                {
                    //get the data at the file index and store in array
                    fileContents[fileIndex] = this.GetData(format, fileIndex);
                }

                //return array of MemoryStreams containing file contents
                return fileContents;
        }

        //use underlying IDataObject to handle getting of data
        return this.underlyingDataObject.GetData(format, autoConvert);
    }

    /// <summary>
    /// Retrieves the data associated with the specified data format at the specified index.
    /// </summary>
    /// <param name="format">The format of the data to retrieve. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <param name="index">The index of the data to retrieve.</param>
    /// <returns>
    /// A <see cref="MemoryStream"/> containing the raw data for the specified data format at the specified index.
    /// </returns>
    public MemoryStream GetData(string format, int index)
    {
        //create a FORMATETC struct to request the data with
        FORMATETC formatetc = new FORMATETC();
        formatetc.cfFormat = (short)DataFormats.GetFormat(format).Id;
        formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
        formatetc.lindex = index;
        formatetc.ptd = new IntPtr(0);
        formatetc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_ISTORAGE | TYMED.TYMED_HGLOBAL;

        //create STGMEDIUM to output request results into
        STGMEDIUM medium = new STGMEDIUM();

        //using the Com IDataObject interface get the data using the defined FORMATETC
        this.comUnderlyingDataObject.GetData(ref formatetc, out medium);

        //retrieve the data depending on the returned store type
        switch (medium.tymed)
        {
            case TYMED.TYMED_ISTORAGE:
                //to handle a IStorage it needs to be written into a second unmanaged
                //memory mapped storage and then the data can be read from memory into
                //a managed byte and returned as a MemoryStream

                NativeMethods.IStorage iStorage = null;
                NativeMethods.IStorage iStorage2 = null;
                NativeMethods.ILockBytes iLockBytes = null;
                System.Runtime.InteropServices.ComTypes.STATSTG iLockBytesStat;
                try
                {
                    //marshal the returned pointer to a IStorage object
                    iStorage = (NativeMethods.IStorage)Marshal.GetObjectForIUnknown(medium.unionmember);
                    Marshal.Release(medium.unionmember);

                    //create a ILockBytes (unmanaged byte array) and then create a IStorage using the byte array as a backing store
                    iLockBytes = NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true);
                    iStorage2 = NativeMethods.StgCreateDocfileOnILockBytes(iLockBytes, 0x00001012, 0);

                    //copy the returned IStorage into the new IStorage
                    iStorage.CopyTo(0, null, IntPtr.Zero, iStorage2);
                    iLockBytes.Flush();
                    iStorage2.Commit(0);

                    //get the STATSTG of the ILockBytes to determine how many bytes were written to it
                    iLockBytesStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
                    iLockBytes.Stat(out iLockBytesStat, 1);
                    int iLockBytesSize = (int)iLockBytesStat.cbSize;

                    //read the data from the ILockBytes (unmanaged byte array) into a managed byte array
                    byte[] iLockBytesContent = new byte[iLockBytesSize];
                    iLockBytes.ReadAt(0, iLockBytesContent, iLockBytesContent.Length, null);

                    //wrapped the managed byte array into a memory stream and return it
                    return new MemoryStream(iLockBytesContent);
                }
                finally
                {
                    //release all unmanaged objects
                    Marshal.ReleaseComObject(iStorage2);
                    Marshal.ReleaseComObject(iLockBytes);
                    Marshal.ReleaseComObject(iStorage);
                }

            case TYMED.TYMED_ISTREAM:
                //to handle a IStream it needs to be read into a managed byte and
                //returned as a MemoryStream

                IStream iStream = null;
                System.Runtime.InteropServices.ComTypes.STATSTG iStreamStat;
                try
                {
                    //marshal the returned pointer to a IStream object
                    iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
                    Marshal.Release(medium.unionmember);

                    //get the STATSTG of the IStream to determine how many bytes are in it
                    iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
                    iStream.Stat(out iStreamStat, 0);
                    int iStreamSize = (int)iStreamStat.cbSize;

                    //read the data from the IStream into a managed byte array
                    byte[] iStreamContent = new byte[iStreamSize];
                    iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);

                    //wrapped the managed byte array into a memory stream and return it
                    return new MemoryStream(iStreamContent);
                }
                finally
                {
                    //release all unmanaged objects
                    Marshal.ReleaseComObject(iStream);
                }

            case TYMED.TYMED_HGLOBAL:
                //to handle a HGlobal the exisitng "GetDataFromHGLOBLAL" method is invoked via
                //reflection

                return (MemoryStream)this.getDataFromHGLOBLALMethod.Invoke(this.oleUnderlyingDataObject, new object[] { DataFormats.GetFormat((short)formatetc.cfFormat).Name, medium.unionmember });
        }

        return null;
    }

    /// <summary>
    /// Determines whether data stored in this instance is associated with, or can be converted to, the specified format.
    /// </summary>
    /// <param name="format">A <see cref="T:System.Type"></see> representing the format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <returns>
    /// true if data stored in this instance is associated with, or can be converted to, the specified format; otherwise, false.
    /// </returns>
    public bool GetDataPresent(Type format)
    {
        return this.underlyingDataObject.GetDataPresent(format);
    }

    /// <summary>
    /// Determines whether data stored in this instance is associated with, or can be converted to, the specified format.
    /// </summary>
    /// <param name="format">The format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <returns>
    /// true if data stored in this instance is associated with, or can be converted to, the specified format; otherwise false.
    /// </returns>
    public bool GetDataPresent(string format)
    {
        return this.underlyingDataObject.GetDataPresent(format);
    }

    /// <summary>
    /// Determines whether data stored in this instance is associated with the specified format, using a Boolean value to determine whether to convert the data to the format.
    /// </summary>
    /// <param name="format">The format for which to check. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <param name="autoConvert">true to determine whether data stored in this instance can be converted to the specified format; false to check whether the data is in the specified format.</param>
    /// <returns>
    /// true if the data is in, or can be converted to, the specified format; otherwise, false.
    /// </returns>
    public bool GetDataPresent(string format, bool autoConvert)
    {
        return this.underlyingDataObject.GetDataPresent(format, autoConvert);
    }

    /// <summary>
    /// Returns a list of all formats that data stored in this instance is associated with or can be converted to.
    /// </summary>
    /// <returns>
    /// An array of the names that represents a list of all formats that are supported by the data stored in this object.
    /// </returns>
    public string[] GetFormats()
    {
        return this.underlyingDataObject.GetFormats();
    }


    public string[] GetFormats(bool autoConvert)
    {
        return this.underlyingDataObject.GetFormats(autoConvert);
    }

    /// <summary>
    /// Stores the specified data in this instance, using the class of the data for the format.
    /// </summary>
    /// <param name="data">The data to store.</param>
    public void SetData(object data)
    {
        this.underlyingDataObject.SetData(data);
    }

    /// <summary>
    /// Stores the specified data and its associated class type in this instance.
    /// </summary>
    /// <param name="format">A <see cref="T:System.Type"></see> representing the format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <param name="data">The data to store.</param>
    public void SetData(Type format, object data)
    {
        this.underlyingDataObject.SetData(format, data);
    }

    /// <summary>
    /// Stores the specified data and its associated format in this instance.
    /// </summary>
    /// <param name="format">The format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <param name="data">The data to store.</param>
    public void SetData(string format, object data)
    {
        this.underlyingDataObject.SetData(format, data);
    }

    /// <summary>
    /// Stores the specified data and its associated format in this instance, using a Boolean value to specify whether the data can be converted to another format.
    /// </summary>
    /// <param name="format">The format associated with the data. See <see cref="T:System.Windows.Forms.DataFormats"></see> for predefined formats.</param>
    /// <param name="autoConvert">true to allow the data to be converted to another format; otherwise, false.</param>
    /// <param name="data">The data to store.</param>
    public void SetData(string format, bool autoConvert, object data)
    {
        this.underlyingDataObject.SetData(format, autoConvert, data);
    }

    #endregion
}