简体   繁体   English

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

[英]Drag and drop large virtual files from C# to Windows Explorer

I have a C# WPF application with a section that serves as an FTP client, listing files on a remote server and allowing the user to download them.我有一个 C# WPF 应用程序,其中有一个部分用作 FTP 客户端,列出远程服务器上的文件并允许用户下载它们。 I want the user to be able to drag and drop files from the file listing onto their own machine (ie into a Windows Explorer shell).我希望用户能够将文件列表中的文件拖放到他们自己的机器上(即拖放到 Windows 资源管理器外壳中)。

To accomplish this, I used the VirtualFileDataObject code from Delay's blog , using the Action<Stream> overload of SetData .为了实现这一点,我使用了Delay 博客中VirtualFileDataObject 代码,使用了SetDataAction<Stream>重载。 This works great on smaller files.这适用于较小的文件。

My problem is: some of the files I'm dealing with are very large (2+ GB), and the way the VirtualFileDataObject class handles the stream involves reading the entire thing into memory, which can end up throwing a "not enough storage" error for those very large files.我的问题是:我正在处理的一些文件非常大(2+ GB), VirtualFileDataObject类处理流的方式涉及将整个文件读入内存,这最终会导致“存储空间不足”那些非常大的文件的错误。

The relevant section of the VirtualFileDataObject code is below. VirtualFileDataObject代码的相关部分如下。 How can I rewrite this code to not require the entire stream to be in memory?如何重写此代码以不要求整个流都在内存中?

    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);
                },
            });
    }

In particular, this section of GetData is the culprit:特别是GetData这部分是罪魁祸首:

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

streamData is the Action<stream> I provide which writes the actual file data to the stream. streamData是我提供的Action<stream> ,它将实际文件数据写入流。 My delegate is just opening a file and reading the bytes into the provided stream.我的委托只是打开一个文件并将字节读入提供的流中。

Is there a way to avoid this last step, perhaps somehow passing the file stream directly to be read from by the Explorer shell?有没有办法避免这最后一步,也许以某种方式直接传递文件流以供 Explorer shell 读取? I'm thinking something like replacing iStream with a pointer to the .NET filestream I've got...but I don't know enough about COM interop to even know the syntax for doing that.我正在考虑用指向我拥有的 .NET 文件流的指针替换iStream类的事情……但我对 COM 互操作的了解还不够,甚至不知道这样做的语法。 Any tips/direction would be appreciated!任何提示/方向将不胜感激!

After more Googling and stumbling around and trying one thing and another, I've got something that works, but I'm still open to better solutions.经过更多的谷歌搜索和绊脚石并尝试一件事和另一件事之后,我得到了一些有效的方法,但我仍然愿意接受更好的解决方案。 For now, when the drop operation happens I'm retrieving the file to a temporary location then using SHCreateStreamOnFileEx to open an IStream to that location.现在,当删除操作发生时,我将文件检索到一个临时位置,然后使用SHCreateStreamOnFileEx打开到该位置的IStream The revised part, the GetData lambda, is as follows:修改后的部分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);
}

As I say, I don't know whether this is the best way to do it or if I could manage it more cleanly, but this seems to work.正如我所说,我不知道这是否是最好的方法,或者我是否可以更干净地管理它,但这似乎有效。

I got the same issue, easy to fix though ;)我遇到了同样的问题,不过很容易解决;)

The problem is that we are creating a new memory stream whereas it is not needed since we already have ours.问题是我们正在创建一个新的内存流,而它是不需要的,因为我们已经有了。 You can create in c# an Stream wrapper that implements IStream:您可以在 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();
   }
}

And then in the VirtualFileDataObject class, change the signature of the SetData method so that you pass now a Stream:然后在 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);
 ...
}

Now, no new memory stream will be created.现在,不会创建新的内存流。

For further info, go to 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 and read my comments有关更多信息,请访问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并阅读我的评论

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何创建“Shell IDList Array”以支持将虚拟文件从C#拖放到Windows资源管理器? - How to create a “Shell IDList Array” to support drag-and-drop of virtual files from C# to Windows Explorer? 如何在C#中将文件夹/文件从一个Windows资源管理器拖放到另一个Windows资源管理器? - How to drag and drop folders/files from One Windows Explorer to another windows explorer in C#? WPF:将虚拟文件拖放到Windows资源管理器中 - WPF: Drag and drop virtual files into Windows explorer 将文件夹从Windows资源管理器拖放到C#中的listBox - Drag and Drop a Folder from Windows Explorer to listBox in C# 使用IStorage / IStream从C#拖放到Windows资源管理器 - Drag and drop from C# to Windows Explorer with IStorage/IStream 从Windows资源管理器拖放到实际适用于Windows 7的自定义C#应用程序 - Drag and drop from windows explorer to custom C# app that actually works with Windows 7 如何允许从Windows资源管理器拖放到C#WPF应用程序中? - How can I allow Drag and Drop from Windows Explorer into a C# WPF application? WPF将文件从Windows资源管理器拖放到TreeView上 - WPF drag and drop files onto TreeView from windows explorer 从资源管理器中拖放不适用于C#Winforms App - Drag and Drop from Explorer not working for C# Winforms App 将文件从ListView拖放到Windows资源管理器? - Drag & Drop file from listview to windows explorer?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM