将大型虚拟文件从C#拖放到Windows资源管理器中

时间:2012-09-13 15:47:22

标签: c# com drag-and-drop

我有一个C#WPF应用程序,其中一个部分充当FTP客户端,列出远程服务器上的文件并允许用户下载它们。我希望用户能够将文件列表中的文件拖放到他们自己的机器上(即进入Windows资源管理器外壳)。

为实现此目的,我使用Action<Stream>,使用SetData的{​​{1}}重载。这适用于较小的文件。

我的问题是:我正在处理的一些文件非常大(2+ GB),而VirtualFileDataObject类处理流的方式涉及将整个内容读入内存,最终可能会为那些非常大的文件抛出“没有足够的存储空间”错误。

VirtualFileDataObject代码的相关部分如下。 如何重写此代码以不要求整个流都在内存中?

    public void SetData(short dataFormat, int index, Action<Stream> streamData) {
        _dataObjects.Add(
            new DataObject {
                FORMATETC = new FORMATETC {
                    cfFormat = dataFormat,
                    ptd = IntPtr.Zero,
                    dwAspect = DVASPECT.DVASPECT_CONTENT,
                    lindex = index,
                    tymed = TYMED.TYMED_ISTREAM
                },
                GetData = () => {
                    // Create IStream for data
                    var ptr = IntPtr.Zero;
                    var iStream = NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true);
                    if (streamData != null) {
                        // Wrap in a .NET-friendly Stream and call provided code to fill it
                        using (var stream = new IStreamWrapper(iStream)) {
                            streamData(stream);
                        }
                    }
                    // Return an IntPtr for the IStream
                    ptr = Marshal.GetComInterfaceForObject(iStream, typeof(IStream));
                    Marshal.ReleaseComObject(iStream);
                    return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
                },
            });
    }

特别是GetData的这一部分是罪魁祸首:

// Wrap in a .NET-friendly Stream and call provided code to fill it
using (var stream = new IStreamWrapper(iStream)) {
    streamData(stream);
}

streamData是我提供的Action<stream>,它将实际的文件数据写入流。我的代表只是打开一个文件并将字节读入提供的流中。

有没有办法避免这最后一步,或许以某种方式直接传递文件流以便从Explorer shell读取?我正在考虑将iStream替换为我已经获得的.NET文件流的指针......但我对COM interop知之甚少,甚至不知道这样做的语法。任何提示/方向将不胜感激!

2 个答案:

答案 0 :(得分:2)

经过更多谷歌搜索和磕磕绊绊,尝试一件事,我有一些有用的东西,但我仍然愿意接受更好的解决方案。目前,当丢弃操作发生时,我正在将文件检索到临时位置,然后使用SHCreateStreamOnFileEx向该位置打开IStream。修订后的部分GetData lambda如下:

GetData = () => {
    var filename = getFilename();

    IStream stream = null;
    NativeMethods.SHCreateStreamOnFileEx(filename, NativeMethods.STGM_FAILIFTHERE, NativeMethods.FILE_ATTRIBUTE_NORMAL, false, null, ref stream);
    var ptr = Marshal.GetComInterfaceForObject(stream, typeof(IStream));
    Marshal.ReleaseComObject(stream);
    return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
}

正如我所说,我不知道这是否是最好的方式,或者我是否可以更干净地管理它,但这似乎有效。

答案 1 :(得分:2)

我遇到了同样的问题,但很容易解决;)

问题是我们正在创建一个新的内存流,而因为我们已经拥有了它,所以不需要它。您可以在c#中创建一个实现IStream的Stream包装器:

    /// <summary>
/// Simple class that exposes a read-only Stream as a IStream.
/// </summary>
private class StreamWrapper : IStream
{

   private Stream _stream;

   public StreamWrapper(Stream stream)

   {
       _stream = stream;
   }

   public void Read(byte[] pv, int cb, System.IntPtr pcbRead)

   {
       Marshal.WriteInt32(pcbRead, _stream.Read(pv, 0, cb));
   }

   public void Seek(long dlibMove, int dwOrigin, System.IntPtr plibNewPosition)

   {
       Marshal.WriteInt32(plibNewPosition, (int)_stream.Seek(dlibMove, (SeekOrigin)dwOrigin));
   }

   public void Clone(out IStream ppstm)

   {
       throw new NotImplementedException();
   }

   public void Commit(int grfCommitFlags)

   {
       throw new NotImplementedException();
   }

   public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)

   {
       throw new NotImplementedException();
   }

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

   {
       throw new NotImplementedException();
   }

   public void Revert()

   {
       throw new NotImplementedException();
   }

   public void SetSize(long libNewSize)

   {
       throw new NotImplementedException();
   }

   public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)

   {
       throw new NotImplementedException();
   }

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

   {
       throw new NotImplementedException();
   }

   public void Write(byte[] pv, int cb, IntPtr pcbWritten)

   {
       throw new NotImplementedException();
   }
}

然后在VirtualFileDataObject类中,更改SetData方法的签名,以便现在传递一个Stream:

public void SetData(short dataFormat, int index, Stream stream)
{
  ...
  var iStream = new StreamWrapper(stream);
  ...
  // Ensure the following line is commented out:
  //Marshal.ReleaseComObject(iStream);
  return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
 ...
}

现在,不会创建新的内存流。

有关详细信息,请转到http://blogs.msdn.com/b/delay/archive/2009/11/04/creating-something-from-nothing-asynchronously-developer-friendly-virtual-file-implementation-for-net-improved.aspx#10496772并阅读我的评论