Click here to Skip to main content
15,887,350 members
Articles / Productivity Apps and Services / Microsoft Office
Article

Outlook Drag and Drop in C#

Rate me:
Please Sign up or sign in to vote.
4.93/5 (89 votes)
28 Jul 2008CPOL6 min read 600.5K   12.3K   114   174
How to drag and drop multiple Outlook mail messages or message attachments to a C# WinForm.

Demo application form

Introduction

A project came up at work the other day requiring the ability to drag and drop any number of mail messages or mail message attachments from Otlook into a WinForms application... Easy I thought, this has to be a common problem. I will just jump on CodeProject, find an example, and with a bit of tweaking, be running in no time. Now, if you are here, I am sure you now know how little information there is available on this topic, so I thought I would add to the very small pool with a complete example of how to drag and drop mail items or attachments from Outlook into a WinForms application without using the Outlook object model.

I am going to skip over explaining all the creating a form, allowing drag and drop, etc., stuff that you can find a million places on the Internet, and just focus on the code to make drag and drop from Outlook work. If you feel lost, go find another article with more basic drag and drop information, get a fully functional drag and drop app, then come back here and work from that code.

Using the Code

When I started writing the code, I decided the easiest way to work the functionality into the existing application was to create a new class that implemented the IDataObject interface that is normally provided when dragging and dropping onto a WinForm. The new class was to catch any calls to Outlook specific data formats, and pass all other calls through to the original IDataObject. Below, you can see how easy it is to use the class in a DragDrop event handler. The FileGroupDescriptor format returns a string array containing the names of each file dropped instead of the usual MemoryStream you would be used to if you have tackled this yourself, and the FileContents returns a MemoryStream array containing the binary contents of each file dropped.

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
    //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");

    for (int fileIndex = 0; fileIndex < filenames.Length; fileIndex++)
    {
        //use the fileindex to get the name and data stream
        string filename = 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();
    }
}

Understanding the Code

To understand what the OutlookDataObject class above is doing to get the file information, there are two things to take note of. The first is that information for the file names is returned from Outlook in a MemoryStream, which is actually a representation of the FILEGROUPDESCRIPTORA or FILEGROUPDESCRIPTORW structures. The second is that to get the file contents, you need the ability to specify an index to get anything past the first file, and the standard IDataObject does not expose this ability. All this is explained in detail below.

Getting the File Names

There are two versions of the file details returned from the IDataObject in the FileGroupDescriptor and FileGroupDescriptorW formats which map to the FILEGROUPDESCRIPTORA and FILEGROUPDESCRIPTORW structures, respectively. In this article, I will focus on the FileGroupDescriptor format which is the ASCII version; FileGroupDescriptorW (W for wide) is the Unicode version, and you will need to use it when working with non-ASCII file names, but they are handled in the same way.

C#
//use the IDataObject to get the FileGroupDescriptor as a MemoryStream
MemoryStream fileGroupDescriptorStream = (MemoryStream)e.Data.GetData("FileGroupDescriptor");

Most examples you will see involve taking the MemoryStream above and converting each non-null byte from index 76 onwards to a char and appending that to a string. While this works adequately for one file drop, it gets a bit tricky when dropping more than that. The correct way is to take the returned bytes and cast it to a FILEGROUPDESCRIPTORA structure, which holds a count of items and an array of FILEDESCRIPTORA structures, which holds the file details. The definitions of these structures can be seen below.

C#
[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;
}

Now that the foundations are set, let's get into the code to actually convert the returned MemoryStream into something usable like a string array of file names. This involves putting the raw bytes into unmanaged memory and using Marshal.PtrToStructure to bring it back in as a structure. There is some extra marshalling in the code because the fgd array of the FILEGROUPDESCRIPTORA structure doesn't get populated as the PtrToStructure method doesn't work with variable length arrays.

C#
//use IDataObject to get FileGroupDescriptor
//as a MemoryStream and copy into a byte array
MemoryStream fgdStream = 
  (MemoryStream)e.Data.GetData("FileGroupDescriptor");
byte[] fgdBytes = new byte[fgdStream.Length];
fgdStream.Read(fgdBytes, 0, fgdBytes.Length);
fgdStream.Close();

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

//marshal the unmanaged memory to a FILEGROUPDESCRIPTORA struct
object fgdObj = Marshal.PtrToStructure(fgdaPtr, 
                        typeof(NativeMethods.FILEGROUPDESCRIPTORA));
NativeMethods.FILEGROUPDESCRIPTORA fgd = 
             (NativeMethods.FILEGROUPDESCRIPTORA)fgdObj;

//create a array to store file names in
string[] fileNames = new string[fgd.cItems];

//get the pointer to the first file descriptor
IntPtr fdPtr = (IntPtr)((int)fgdaPointer + Marshal.SizeOf(fgdaPointer));

//loop for the number of files acording to the file group descriptor
for(int fdIndex = 0;fdIndex < fgd.cItems;fdIndex++)
{
    //marshal the pointer to the file descriptor as a FILEDESCRIPTORA struct
    object fdObj = Marshal.PtrToStructure(fdPtr, 
                          typeof(NativeMethods.FILEDESCRIPTORA));
    NativeMethods.FILEDESCRIPTORA fd = (NativeMethods.FILEDESCRIPTORA)fdObj;
    
    //get file name of file descriptor and put in array
    fileNames[fdIndex] = fd.cFileName;

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

At this point, we have now converted the MemoryStream into a string array that contains the name of each file dropped, which is a lot easier to work with. Outlook messages get the name of their subject with ".msg" on the end, and for Outlook message attachments, the file name of the attachment.

Getting the File Contents

The file contents sit behind the FileContents format. If you drag and drop a single attachment, then the default IDataObject works as expected and will return a MemoryStream containing that file's data. Things get more complex when dragging multiple attachments or Outlook email messages for different reasons. Multiple attachments pose an issue because the OS calls for drop data allow for an index to be specified, but the C# implementation of the IDataObject doesn't expose this directly. Mail messages are an issue because the OS call returns an IStorage which is a compound file type, and again, the C# implementation of the IDataObject lets us down by not handling this type of return, so you get a null.

Specifying an Index

To get at the content of multiple dropped files, an index needs to be specified to indicate which file contents are required. The default IDataObject doesn't allow this, but it can be cast to a COM IDataObject which will accept a FORMATETC structure that has an index property that can be set to indicate the file contents required.

C#
//cast the default IDataObject to a com IDataObject
System.Runtime.InteropServices.ComTypes.IDataObject comDataObject;
comDataObject = (System.Runtime.InteropServices.ComTypes.IDataObject)e.Data;

//create a FORMATETC struct to request the data with from the com IDataObject
FORMATETC formatetc = new FORMATETC();
formatetc.cfFormat = (short)DataFormats.GetFormat(format).Id;
formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
formatetc.lindex = 0; //zero based index to retrieve
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
comDataObject.GetData(ref formatetc, out medium);

As you can see in the example above, by changing the value of the lindex property of the FORMATETC structure, we can change the index of the file contents to retrieve. The result of the call is sitting in the STGMEDIUM structure; this contains a pointer to the actual result in the unionmember property, and the type of result at the pointer in the tymed property. There are three types of returns available to the STGMEDIUM, and each one is explained below.

The Stream Result (TYMED_ISTREAM)

If the tymed property of the STGMEDIUM is TYMED_ISTREAM, then the result is a stream. This is normally handled by the default IDataObject, but when working with the COM IDataObject, the handling code needs to be written again.

C#
//marshal the returned pointer to a IStream object
IStream 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
Stream filestream = new MemoryStream(iStreamContent);

The Storage Result (TYMED_ISTORAGE)

If the tymed property of the STGMEDIUM is TYMED_ISTORAGE, then the result is a storage which is a compound file type. This is a little more complex to process than the stream as it needs to be copied into a memory backed IStorage so its data can then be read from the backing memory store.

C#
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)
    iLockBytes = NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, true);
    
    //create a IStorage using the ILockBytes 
    //(unmanaged byte array) as a backing store
    iStorage2 = NativeMethods.StgCreateDocfileOnILockBytes(iLockBytes, 
                                                       0x00001012, 0);

    //copy the returned IStorage into the new memory backed 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
    Stream filestream = new MemoryStream(iStreamContent);
}
finally
{
    //release all unmanaged objects
    Marshal.ReleaseComObject(iStorage2);
    Marshal.ReleaseComObject(iLockBytes);
    Marshal.ReleaseComObject(iStorage);
}

The HGlobal Result (TYMED_HGLOBAL)

If the tymed property of the STGMEDIUM is TYMED_HGLOBAL, then the result is stored in a HGlobal. For the purposes of Outlook drag and drop, this type should never be returned, but for completeness, I use a little bit of Reflection on the original IDataObject to have that class handle it.

C#
//get the internal ole dataobject and its GetDataFromHGLOBLAL method
BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
FieldInfo innerDataField = 
  e.Data.GetType().GetField("innerData", bindingFlags);
IDataObject oleDataObject = 
  (System.Windows.Forms.IDataObject)innerDataField.GetValue(e.Data);
MethodInfo getDataFromHGLOBLALMethod = 
  oleDataObject.GetType().GetMethod("GetDataFromHGLOBLAL", bindingFlags);

getDataFromHGLOBLALMethod.Invoke(oleDataObject, 
  new object[] { format, medium.unionmember });

Conclusion

Well, hopefully, that all helps someone. I have a few other Outlook tricks that I will be doing articles for; one is how to extract and save a message attachment without using the object model, another is how to use the code in this article to enable drag and drop of Outlook messages and attachments into IE (with appropriate security, so only good for the intranet).

History

  • 1 July 2008:
    • Original article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Guava Development
Australia Australia
I am the Founder of Guava Development a Software Services Company located in Perth, Western Australia dedicated to improving productivity and reducing costs through the targeted and innovative application of software assisted workflows and packages.

I have been working in the industry for 10 years. My day job usually involves programming with C# but I have been known to mess around with just about everything.

Comments and Discussions

 
QuestionGetDataFromHGLOBLAL Fails => change to GetDataFromHGLOBAL Pin
Wolleschorge21-Dec-22 2:05
Wolleschorge21-Dec-22 2:05 
QuestionVB.NET Conversion (usable) Pin
Navlis Lukas7-Jul-22 1:49
Navlis Lukas7-Jul-22 1:49 
Should work with correct filenames (64 bit) and short conversion in GetData.
Happy coding Smile | :)

VB.NET
Imports System.IO
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes
Imports System.Windows.Forms

Public Class OutlookDataObject
  'Inherits System.Windows.Forms.IDataObject

  Private Class NativeMethods
    <DllImport("kernel32.dll")>
    Private Shared Function GlobalLock(ByVal hMem As IntPtr) As IntPtr
    End Function
    <DllImport("ole32.dll", PreserveSig:=False)>
    Public Shared Function CreateILockBytesOnHGlobal(ByVal hGlobal As IntPtr, ByVal fDeleteOnRelease As Boolean) As ILockBytes
    End Function
    <DllImport("OLE32.DLL", CharSet:=CharSet.Auto, PreserveSig:=False)>
    Public Shared Function GetHGlobalFromILockBytes(ByVal pLockBytes As ILockBytes) As IntPtr
    End Function
    <DllImport("OLE32.DLL", CharSet:=CharSet.Unicode, PreserveSig:=False)>
    Public Shared Function StgCreateDocfileOnILockBytes(ByVal plkbyt As ILockBytes, ByVal grfMode As UInteger, ByVal reserved As UInteger) As IStorage
    End Function

    <ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("0000000B-0000-0000-C000-000000000046")>
    Interface IStorage
      Function CreateStream(
            <[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal grfMode As Integer,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved1 As Integer,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved2 As Integer) As <MarshalAs(UnmanagedType.Interface)> IStream
      Function OpenStream(
            <[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String, ByVal reserved1 As IntPtr,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal grfMode As Integer,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved2 As Integer) As <MarshalAs(UnmanagedType.Interface)> IStream
      Function CreateStorage(
            <[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal grfMode As Integer,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved1 As Integer,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved2 As Integer) As <MarshalAs(UnmanagedType.Interface)> IStorage
      Function OpenStorage(
            <[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String, ByVal pstgPriority As IntPtr,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal grfMode As Integer, ByVal snbExclude As IntPtr,
            <[In], MarshalAs(UnmanagedType.U4)> ByVal reserved As Integer) As <MarshalAs(UnmanagedType.Interface)> IStorage
      Sub CopyTo(ByVal ciidExclude As Integer,
<[In], MarshalAs(UnmanagedType.LPArray)> ByVal pIIDExclude As Guid(), ByVal snbExclude As IntPtr,
<[In], MarshalAs(UnmanagedType.[Interface])> ByVal stgDest As IStorage)
      Sub MoveElementTo(
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String,
<[In], MarshalAs(UnmanagedType.[Interface])> ByVal stgDest As IStorage,
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsNewName As String,
<[In], MarshalAs(UnmanagedType.U4)> ByVal grfFlags As Integer)
      Sub Commit(ByVal grfCommitFlags As Integer)
      Sub Revert()
      Sub EnumElements(
<[In], MarshalAs(UnmanagedType.U4)> ByVal reserved1 As Integer, ByVal reserved2 As IntPtr,
<[In], MarshalAs(UnmanagedType.U4)> ByVal reserved3 As Integer, <Out>
<MarshalAs(UnmanagedType.[Interface])> ByRef ppVal As Object)
      Sub DestroyElement(
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String)
      Sub RenameElement(
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsOldName As String,
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsNewName As String)
      Sub SetElementTimes(
<[In], MarshalAs(UnmanagedType.BStr)> ByVal pwcsName As String,
<[In]> ByVal pctime As System.Runtime.InteropServices.ComTypes.FILETIME,
<[In]> ByVal patime As System.Runtime.InteropServices.ComTypes.FILETIME,
<[In]> ByVal pmtime As System.Runtime.InteropServices.ComTypes.FILETIME)
      Sub SetClass(
<[In]> ByRef clsid As Guid)
      Sub SetStateBits(ByVal grfStateBits As Integer, ByVal grfMask As Integer)
      Sub Stat(<Out>
ByRef pStatStg As System.Runtime.InteropServices.ComTypes.STATSTG, ByVal grfStatFlag As Integer)
    End Interface

    <ComImport, Guid("0000000A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
    Interface ILockBytes
      Sub ReadAt(
<[In], MarshalAs(UnmanagedType.U8)> ByVal ulOffset As Long,
<Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=1)> ByVal pv As Byte(),
<[In], MarshalAs(UnmanagedType.U4)> ByVal cb As Integer,
<Out, MarshalAs(UnmanagedType.LPArray)> ByVal pcbRead As Integer())
      Sub WriteAt(
<[In], MarshalAs(UnmanagedType.U8)> ByVal ulOffset As Long, ByVal pv As IntPtr,
<[In], MarshalAs(UnmanagedType.U4)> ByVal cb As Integer,
<Out, MarshalAs(UnmanagedType.LPArray)> ByVal pcbWritten As Integer())
      Sub Flush()
      Sub SetSize(
<[In], MarshalAs(UnmanagedType.U8)> ByVal cb As Long)
      Sub LockRegion(
<[In], MarshalAs(UnmanagedType.U8)> ByVal libOffset As Long,
<[In], MarshalAs(UnmanagedType.U8)> ByVal cb As Long,
<[In], MarshalAs(UnmanagedType.U4)> ByVal dwLockType As Integer)
      Sub UnlockRegion(
<[In], MarshalAs(UnmanagedType.U8)> ByVal libOffset As Long,
<[In], MarshalAs(UnmanagedType.U8)> ByVal cb As Long,
<[In], MarshalAs(UnmanagedType.U4)> ByVal dwLockType As Integer)
      Sub Stat(<Out>
ByRef pstatstg As System.Runtime.InteropServices.ComTypes.STATSTG,
<[In], MarshalAs(UnmanagedType.U4)> ByVal grfStatFlag As Integer)
    End Interface

    <StructLayout(LayoutKind.Sequential)>
    Public NotInheritable Class POINTL
      Public x As Integer
      Public y As Integer
    End Class

    <StructLayout(LayoutKind.Sequential)>
    Public NotInheritable Class SIZEL
      Public cx As Integer
      Public cy As Integer
    End Class

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
    Public NotInheritable Class FILEGROUPDESCRIPTORA
      Public cItems As UInteger
      Public fgd As FILEDESCRIPTORA()
    End Class

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)>
    Public NotInheritable Class FILEDESCRIPTORA
      Public dwFlags As UInteger
      Public clsid As Guid
      Public sizel As SIZEL
      Public pointl As POINTL
      Public dwFileAttributes As UInteger
      Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public nFileSizeHigh As UInteger
      Public nFileSizeLow As UInteger
      <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
      Public cFileName As String
    End Class

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
    Public NotInheritable Class FILEGROUPDESCRIPTORW
      Public cItems As UInteger
      Public fgd As FILEDESCRIPTORW()
    End Class

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
    Public NotInheritable Class FILEDESCRIPTORW
      Public dwFlags As UInteger
      Public clsid As Guid
      Public sizel As SIZEL
      Public pointl As POINTL
      Public dwFileAttributes As UInteger
      Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
      Public nFileSizeHigh As UInteger
      Public nFileSizeLow As UInteger
      <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)>
      Public cFileName As String
    End Class
  End Class

  Private underlyingDataObject As System.Windows.Forms.IDataObject
  Private comUnderlyingDataObject As System.Runtime.InteropServices.ComTypes.IDataObject
  Private oleUnderlyingDataObject As System.Windows.Forms.IDataObject
  Private getDataFromHGLOBLALMethod As MethodInfo

  Public Sub New(ByVal underlyingDataObject As System.Windows.Forms.IDataObject)
    Me.underlyingDataObject = underlyingDataObject
    Me.comUnderlyingDataObject = CType(Me.underlyingDataObject, System.Runtime.InteropServices.ComTypes.IDataObject)
    Dim innerDataField As FieldInfo = Me.underlyingDataObject.[GetType]().GetField("innerData", BindingFlags.NonPublic Or BindingFlags.Instance)
    Me.oleUnderlyingDataObject = CType(innerDataField.GetValue(Me.underlyingDataObject), System.Windows.Forms.IDataObject)
    Me.getDataFromHGLOBLALMethod = Me.oleUnderlyingDataObject.[GetType]().GetMethod("GetDataFromHGLOBLAL", BindingFlags.NonPublic Or BindingFlags.Instance)
  End Sub

  Public Function GetData(ByVal format As Type) As Object
    Return Me.GetData(format.FullName)
  End Function

  Public Function GetData(ByVal format As String) As Object
    Return Me.GetData(format, True)
  End Function

  Public Function GetData(ByVal format As String, ByVal autoConvert As Boolean) As Object
    Select Case format
      Case "FileGroupDescriptor"
        Dim fileGroupDescriptorAPointer As IntPtr = IntPtr.Zero

        Try
          Dim fileGroupDescriptorStream As MemoryStream = CType(Me.underlyingDataObject.GetData("FileGroupDescriptor", autoConvert), MemoryStream)
          Dim fileGroupDescriptorBytes As Byte() = New Byte(CInt(fileGroupDescriptorStream.Length - 1)) {}
          fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length)
          fileGroupDescriptorStream.Close()
          fileGroupDescriptorAPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length)
          Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorAPointer, fileGroupDescriptorBytes.Length)
          Dim fileGroupDescriptorObject As Object = Marshal.PtrToStructure(fileGroupDescriptorAPointer, GetType(NativeMethods.FILEGROUPDESCRIPTORA))
          Dim fileGroupDescriptor As NativeMethods.FILEGROUPDESCRIPTORA = CType(fileGroupDescriptorObject, NativeMethods.FILEGROUPDESCRIPTORA)
          Dim fileNames As String() = New String(CInt(fileGroupDescriptor.cItems - 1)) {}
          Dim fileDescriptorPointer As IntPtr = fileGroupDescriptorAPointer + Marshal.SizeOf(fileGroupDescriptor.cItems)

          For fileDescriptorIndex As Integer = 0 To CInt(fileGroupDescriptor.cItems - 1)
            Dim fileDescriptor As NativeMethods.FILEDESCRIPTORA = CType(Marshal.PtrToStructure(fileDescriptorPointer, GetType(NativeMethods.FILEDESCRIPTORA)), NativeMethods.FILEDESCRIPTORA)
            fileNames(fileDescriptorIndex) = fileDescriptor.cFileName
            fileDescriptorPointer = fileDescriptorPointer + Marshal.SizeOf(fileDescriptor)
          Next

          Return fileNames
        Finally
          Marshal.FreeHGlobal(fileGroupDescriptorAPointer)
        End Try

      Case "FileGroupDescriptorW"
        Dim fileGroupDescriptorWPointer As IntPtr = IntPtr.Zero

        Try
          Dim fileGroupDescriptorStream As MemoryStream = CType(Me.underlyingDataObject.GetData("FileGroupDescriptorW"), MemoryStream)
          Dim fileGroupDescriptorBytes As Byte() = New Byte(CInt(fileGroupDescriptorStream.Length - 1)) {}
          fileGroupDescriptorStream.Read(fileGroupDescriptorBytes, 0, fileGroupDescriptorBytes.Length)
          fileGroupDescriptorStream.Close()
          fileGroupDescriptorWPointer = Marshal.AllocHGlobal(fileGroupDescriptorBytes.Length)
          Marshal.Copy(fileGroupDescriptorBytes, 0, fileGroupDescriptorWPointer, fileGroupDescriptorBytes.Length)
          Dim fileGroupDescriptorObject As Object = Marshal.PtrToStructure(fileGroupDescriptorWPointer, GetType(NativeMethods.FILEGROUPDESCRIPTORW))
          Dim fileGroupDescriptor As NativeMethods.FILEGROUPDESCRIPTORW = CType(fileGroupDescriptorObject, NativeMethods.FILEGROUPDESCRIPTORW)
          Dim fileNames As String() = New String(CInt(fileGroupDescriptor.cItems - 1)) {}
          Dim fileDescriptorPointer As IntPtr = fileGroupDescriptorWPointer + Marshal.SizeOf(fileGroupDescriptor.cItems)

          For fileDescriptorIndex As Integer = 0 To CInt(fileGroupDescriptor.cItems - 1)
            Dim fileDescriptor As NativeMethods.FILEDESCRIPTORW = CType(Marshal.PtrToStructure(fileDescriptorPointer, GetType(NativeMethods.FILEDESCRIPTORW)), NativeMethods.FILEDESCRIPTORW)
            fileNames(fileDescriptorIndex) = fileDescriptor.cFileName
            fileDescriptorPointer = fileDescriptorPointer + Marshal.SizeOf(fileDescriptor)
          Next

          Return fileNames
        Finally
          Marshal.FreeHGlobal(fileGroupDescriptorWPointer)
        End Try

      Case "FileContents"
        Dim fileContentNames As String() = CType(Me.GetData("FileGroupDescriptor"), String())
        Dim fileContents As MemoryStream() = New MemoryStream(fileContentNames.Length - 1) {}

        For fileIndex As Integer = 0 To fileContentNames.Length - 1
          fileContents(fileIndex) = Me.GetData(format, fileIndex)
        Next

        Return fileContents
    End Select

    Return Me.underlyingDataObject.GetData(format, autoConvert)
  End Function

  Public Function GetData(ByVal format As String, ByVal index As Integer) As MemoryStream
    'Dim formatetc As FORMATETC = New FORMATETC()
    'formatetc.cfFormat = CShort(DataFormats.GetFormat(format).Id)

    Dim formatetc As FORMATETC = New FORMATETC()
    Dim iInteger As Integer = DataFormats.GetFormat(format).Id

    If (iInteger > 32767) Then
      iInteger -= 65536
    End If

    formatetc.cfFormat = CShort(iInteger)
    formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT
    formatetc.lindex = index
    formatetc.ptd = New IntPtr(0)
    formatetc.tymed = TYMED.TYMED_ISTREAM Or TYMED.TYMED_ISTORAGE Or TYMED.TYMED_HGLOBAL
    Dim medium As STGMEDIUM = New STGMEDIUM()
    Me.comUnderlyingDataObject.GetData(formatetc, medium)

    Select Case medium.tymed
      Case TYMED.TYMED_ISTORAGE
        Dim iStorage As NativeMethods.IStorage = Nothing
        Dim iStorage2 As NativeMethods.IStorage = Nothing
        Dim iLockBytes As NativeMethods.ILockBytes = Nothing
        Dim iLockBytesStat As System.Runtime.InteropServices.ComTypes.STATSTG

        Try
          iStorage = CType(Marshal.GetObjectForIUnknown(medium.unionmember), NativeMethods.IStorage)
          Marshal.Release(medium.unionmember)
          iLockBytes = NativeMethods.CreateILockBytesOnHGlobal(IntPtr.Zero, True)
          iStorage2 = NativeMethods.StgCreateDocfileOnILockBytes(iLockBytes, &H1012, 0)
          iStorage.CopyTo(0, Nothing, IntPtr.Zero, iStorage2)
          iLockBytes.Flush()
          iStorage2.Commit(0)
          iLockBytesStat = New System.Runtime.InteropServices.ComTypes.STATSTG()
          iLockBytes.Stat(iLockBytesStat, 1)
          Dim iLockBytesSize As Integer = CInt(iLockBytesStat.cbSize)
          Dim iLockBytesContent As Byte() = New Byte(iLockBytesSize - 1) {}
          iLockBytes.ReadAt(0, iLockBytesContent, iLockBytesContent.Length, Nothing)
          Return New MemoryStream(iLockBytesContent)
        Finally
          Marshal.ReleaseComObject(iStorage2)
          Marshal.ReleaseComObject(iLockBytes)
          Marshal.ReleaseComObject(iStorage)
        End Try

      Case TYMED.TYMED_ISTREAM
        Dim iStream As IStream = Nothing
        Dim iStreamStat As System.Runtime.InteropServices.ComTypes.STATSTG

        Try
          iStream = CType(Marshal.GetObjectForIUnknown(medium.unionmember), IStream)
          Marshal.Release(medium.unionmember)
          iStreamStat = New System.Runtime.InteropServices.ComTypes.STATSTG()
          iStream.Stat(iStreamStat, 0)
          Dim iStreamSize As Integer = CInt(iStreamStat.cbSize)
          Dim iStreamContent As Byte() = New Byte(iStreamSize - 1) {}
          iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero)
          Return New MemoryStream(iStreamContent)
        Finally
          Marshal.ReleaseComObject(iStream)
        End Try

      Case TYMED.TYMED_HGLOBAL
        Return CType(Me.getDataFromHGLOBLALMethod.Invoke(Me.oleUnderlyingDataObject, New Object() {DataFormats.GetFormat(CShort(formatetc.cfFormat)).Name, medium.unionmember}), MemoryStream)
    End Select

    Return Nothing
  End Function

  Public Function GetDataPresent(ByVal format As Type) As Boolean
    Return Me.underlyingDataObject.GetDataPresent(format)
  End Function

  Public Function GetDataPresent(ByVal format As String) As Boolean
    Return Me.underlyingDataObject.GetDataPresent(format)
  End Function

  Public Function GetDataPresent(ByVal format As String, ByVal autoConvert As Boolean) As Boolean
    Return Me.underlyingDataObject.GetDataPresent(format, autoConvert)
  End Function

  Public Function GetFormats() As String()
    Return Me.underlyingDataObject.GetFormats()
  End Function

  Public Function GetFormats(ByVal autoConvert As Boolean) As String()
    Return Me.underlyingDataObject.GetFormats(autoConvert)
  End Function

  Public Sub SetData(ByVal data As Object)
    Me.underlyingDataObject.SetData(data)
  End Sub

  Public Sub SetData(ByVal format As Type, ByVal data As Object)
    Me.underlyingDataObject.SetData(format, data)
  End Sub

  Public Sub SetData(ByVal format As String, ByVal data As Object)
    Me.underlyingDataObject.SetData(format, data)
  End Sub

  Public Sub SetData(ByVal format As String, ByVal autoConvert As Boolean, ByVal data As Object)
    Me.underlyingDataObject.SetData(format, autoConvert, data)
  End Sub
End Class

PraiseWorks very well with some tweaks also with WPF Pin
Anna Fondis11-Mar-22 5:29
Anna Fondis11-Mar-22 5:29 
GeneralMy vote of 5 Pin
RobR Gray16-Dec-21 21:08
RobR Gray16-Dec-21 21:08 
BugNot Working in 64 bit File Name to Short Pin
Robert Schmid10-Aug-21 3:32
Robert Schmid10-Aug-21 3:32 
QuestionFailed to drag the attachment from an Outlook e-mail (Outlook Version 2006, Build 13001.20384 Click-to-Run) Pin
Member 1317454630-Jul-20 4:17
Member 1317454630-Jul-20 4:17 
AnswerRe: Failed to drag the attachment from an Outlook e-mail (Outlook Version 2006, Build 13001.20384 Click-to-Run) Pin
Atron 202130-Jun-21 22:19
Atron 202130-Jun-21 22:19 
SuggestionRe: Failed to drag the attachment from an Outlook e-mail (Outlook Version 2006, Build 13001.20384 Click-to-Run) Pin
Member 99544264-Jul-21 23:40
Member 99544264-Jul-21 23:40 
AnswerRe: Failed to drag the attachment from an Outlook e-mail (Outlook Version 2006, Build 13001.20384 Click-to-Run) - solved Pin
Member 99544262-Jul-21 0:00
Member 99544262-Jul-21 0:00 
QuestionSystem.NotImplementedException , if the application will be executed in remote environment? Pin
Member 1490115428-Jul-20 5:39
Member 1490115428-Jul-20 5:39 
AnswerRe: System.NotImplementedException , if the application will be executed in remote environment? Pin
Navlis Lukas12-Jul-22 4:48
Navlis Lukas12-Jul-22 4:48 
QuestionBug in this code may be exposed by a change in Outlook (April 2020 "2004") update Pin
Scot Brennecke5-Jun-20 9:04
professionalScot Brennecke5-Jun-20 9:04 
AnswerRe: Bug in this code may be exposed by a change in Outlook (April 2020 "2004") update Pin
John Schroedl25-Jun-20 9:38
professionalJohn Schroedl25-Jun-20 9:38 
GeneralRe: Bug in this code may be exposed by a change in Outlook (April 2020 "2004") update Pin
Scot Brennecke25-Jun-20 14:29
professionalScot Brennecke25-Jun-20 14:29 
GeneralRe: Bug in this code may be exposed by a change in Outlook (April 2020 "2004") update Pin
John Schroedl26-Jun-20 2:42
professionalJohn Schroedl26-Jun-20 2:42 
QuestionNative methods code Pin
Triumphman16-Dec-19 0:09
Triumphman16-Dec-19 0:09 
QuestionOutlook Drag and Drop MSG File Pin
Russell Mangel23-Dec-18 13:31
Russell Mangel23-Dec-18 13:31 
QuestionError when drag and drop email messages with more than 255 char() in the subject line on a 64 bit system (win7,8,01) Vb 2017 Pin
AndreChev16-Oct-18 14:23
AndreChev16-Oct-18 14:23 
QuestionConvert it to Vb.net Pin
Member 980410624-Jul-18 5:37
Member 980410624-Jul-18 5:37 
AnswerRe: Convert it to Vb.net Pin
Member 980410625-Jul-18 3:08
Member 980410625-Jul-18 3:08 
Questionfile name too short Pin
gizbernus16-Jul-18 2:03
gizbernus16-Jul-18 2:03 
AnswerRe: file name too short PinPopular
ManikandanChennai19-Jul-18 1:43
professionalManikandanChennai19-Jul-18 1:43 
QuestionArithmetic operation resulted in an overflow Pin
George Hendrickson9-Jul-18 9:10
professionalGeorge Hendrickson9-Jul-18 9:10 
AnswerRe: Arithmetic operation resulted in an overflow Pin
ManikandanChennai19-Jul-18 1:41
professionalManikandanChennai19-Jul-18 1:41 
GeneralRe: Arithmetic operation resulted in an overflow Pin
mrluisrodriguez24-Aug-18 5:42
professionalmrluisrodriguez24-Aug-18 5:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.