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

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

为了实现这一点,我使用了Delay的博客中的VirtualFileDataObject代码 ,使用了SetDataAction重载。 这适用于较小的文件。

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

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

  public void SetData(short dataFormat, int index, Action 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(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 ,它将实际的文件数据写入流。 我的代表只是打开一个文件并将字节读入提供的流中。

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

我有同样的问题,虽然容易修复;)

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

  ///  /// Simple class that exposes a read-only Stream as a IStream. ///  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(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并阅读我的评论

经过更多的谷歌搜索和磕磕绊绊,尝试一件事,我有一些有用的东西,但我仍然愿意接受更好的解决方案。 现在,当drop操作发生时,我正在将文件检索到临时位置,然后使用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(ptr, NativeMethods.S_OK); } 

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