使用.NET 4.5或更高版本,从Outlook中实现C#拖放操作

3

有没有人有C#代码可以接受从Outlook拖放到Winforms应用程序的操作,且该代码适用于.Net Framework 4.5或更高版本?

我有一些代码已经使用了大约12年,其中包括从Outlook拖放项目的功能,包括电子邮件。

当编译目标框架设置为.Net Framework 4时,该代码运行得非常完美。然而,我正在开发的一些新功能需要.Net Framework 4.5或更高版本。这会导致无法从Outlook进行拖放操作。

代码相当复杂,它执行了许多与问题无关的操作,以确定文件的位置和显示对象等信息,因此我不会包含所有内容,但是它出现问题的点非常简单...

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

在.NET 4下,上述代码的最后一行将如预期般返回文件名。
在.NET 4.5或更高版本下,上述代码的最后一行将返回null。
我已在.NET 4.5、4.5.1、4.5.2、4.6、4.6.1、4.6.2和4.7.2(我安装的所有版本)下进行了测试,它们都无法正常工作。
我搜索了几天尝试了所有我能找到的拖放代码,但似乎都无法在.NET 4.5或更高版本下工作。
下面是我编写的一个测试表单的完整代码,其中包含我从这里和其他地方找到的代码。这段代码在.NET Framework 4下完美运行,无论您是从文件还是从Outlook中拖放电子邮件,但在.NET Framework 4.5或更高版本下均无法正常工作(再次测试了所有相同的版本)。
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());
            }
        }
    }
}

好的,看起来这段代码太长了,我们将把OutlookDataObject类放在下面的回复中。

唯一需要注意的是形式本身,它只是一个空的Winforms表格,并设置了以下属性:

AllowDrop : True

DragEnter事件:Form1_DragEnter

DragDrop事件:Form1_DragDrop

如果相关的话,我正在使用Visual Studio 2017专业版,并在Windows 10上运行,尽管拖放不工作也已经被客户报告在运行Windows 7的机器上。

2个回答

6

问题已经解决!

问题的原因是,.Net Framework 4及以下版本使用32位指针,而.Net Framework 4.5及以上版本使用64位指针。

我之前的老代码以及提供的示例代码都是基于指针只有32位的假设编写的。

OutlookDataObject类中更改以下两行代码(并在我的代码中进行类似的更改)即可解决问题:

从:

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

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

致:

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

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

不确定为什么我的Visual Studio没有在遇到这些行时出现错误,但无所谓。

通过上述更改,我提供的示例代码可以处理文件和Outlook电子邮件的拖放操作,无论是单个项目还是多个项目。


谢谢。对于任何想知道的人,需要替换的两行代码将位于OutlookDataObject类中的GetData(string format, bool autoConvert)函数内部。我已经多次修改了这个类,以适应网络上流传的原始代码。 - undefined

1

上面的代码中使用了OutlookDataObject类,因为它太长无法在一个帖子中放置。

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
}

你在32位版本的Outlook上测试拖放了吗?在64位版本上一切正常,但在32位版本上Outlook会立即崩溃。这个问题似乎在其他项目中已经被修复(https://github.com/tonyfederer/OutlookFileDrag/issues/9),但我还无法确定修复此问题所需的代码更改。 - 1024kilobyte
通常情况下,一旦我在StackOverflow上提出问题,不到一个小时就能自己找到解决方案。只需取消注释类“FILEGROUPDESCRIPTORA”和“FILEGROUPDESCRIPTORW”中的数组属性“fgd”,即可在32位和64位Outlook版本上实现拖放功能。 - 1024kilobyte
我在这段代码中遇到了空值错误:case TYMED.TYMED_HGLOBAL: return (MemoryStream)this.getDataFromHGLOBLALMethod.Invoke(this.oleUnderlyingDataObject, new object[] { DataFormats.GetFormat((short)formatetc.cfFormat).Name, medium.unionmember }); - Wolfgang Schorge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接