[英]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 代码,使用了
SetData
的Action<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.