【问题标题】:Drag and drop large virtual files from C# to Windows Explorer将大型虚拟文件从 C# 拖放到 Windows 资源管理器
【发布时间】:2012-09-06 18:26:29
【问题描述】:

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

为此,我使用了VirtualFileDataObject code from Delay's blog,使用了SetDataAction<Stream> 重载。这适用于较小的文件。

我的问题是:我正在处理的一些文件非常大(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&lt;stream&gt;,它将实际文件数据写入流。我的代表只是打开一个文件并将字节读取到提供的流中。

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

【问题讨论】:

    标签: c# com drag-and-drop


    【解决方案1】:

    经过更多的谷歌搜索和跌跌撞撞并尝试一件事又一件事后,我得到了一些可行的方法,但我仍然对更好的解决方案持开放态度。现在,当删除操作发生时,我将文件检索到一个临时位置,然后使用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);
    }
    

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

    【讨论】:

    【解决方案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 并阅读我的 cmets

    【讨论】:

      猜你喜欢
      • 2011-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多