使用 VirtualFileDataObject 通过 IStream 拖放大型虚拟文件

tek*_*orj 5 c# wpf drag-and-drop

我成功地使用了Delay 博客中的 VirtualFileDataObject 代码,但我想避免将整个文件流式传输到内存中。

我在 Stack Overflow 上找到了这个先前回答的问题将大型虚拟文件从 c# 拖放到 Windows 资源管理器 该问题由 matthieu 通过更改 SetData 方法的签名来回答。

这是我的问题,更改 SetData 方法的签名后,调用它的其他地方仍在寻找旧签名。

这是原始的SetData;

   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);
                },
            });
    }
Run Code Online (Sandbox Code Playgroud)

matthieu 建议将其更改为;

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);
 ...
}
Run Code Online (Sandbox Code Playgroud)

在我进行这些更改后,以下调用将不起作用;(这就是我需要帮助的地方)我该如何解决这个问题;

            foreach (var fileDescriptor in fileDescriptors)
        {
            **SetData(FILECONTENTS, index, fileDescriptor.StreamContents);**
            index++;
        }
Run Code Online (Sandbox Code Playgroud)

基本上将“操作流数据”更改为“流流”会导致我的问题。我不确定更改后如何调用它。

所有这些代码都来自 Delays VirtualFileDataObject。我不知道是否应该将其发布在这里。但是,如果您点击上面的链接,它会将您带到该博客,以便您可以查看它。

我太接近了,只是无法弄清楚最后一步,谢谢您的浏览

Sim*_*ier 4

我也遇到过同样的问题。这是我为解决此问题所做的操作(正如您所说,其他答案中尚未完全解决)

1) 修改FileDescriptorStreamContents属性:

public Action<Stream> StreamContents { get; set; }
Run Code Online (Sandbox Code Playgroud)

对此:

public Func<Stream> StreamContents { get; set; }
Run Code Online (Sandbox Code Playgroud)

Stream(我们期望的是我们可以读取的内容,而不是传递客户端Stream可以写入的内容,这正是资源管理器的工作方式和期望)

2)修改SetData方法重载:

public void SetData(short dataFormat, int index, Action<Stream> streamData)
Run Code Online (Sandbox Code Playgroud)

对此:

public void SetData(short dataFormat, int index, Func<Stream> streamData)
Run Code Online (Sandbox Code Playgroud)

3)将SetData代码的GetDatalambda 更改为:

GetData = () =>
{
    ManagedIStream istream = null;
    if (streamData != null)
    {
        Stream stream = streamData();
        if (stream != null)
        {
            istream = new ManagedIStream(stream);
        }
    }

    IntPtr ptr = istream != null ? Marshal.GetComInterfaceForObject(istream, typeof(IStream)) : IntPtr.Zero;
    return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
},
Run Code Online (Sandbox Code Playgroud)

ManagedIStream4)在代码中添加这个类(也可以IStreamWrapper完全删除该类)

private class ManagedIStream : IStream
{
    private Stream _stream;

    public ManagedIStream(Stream stream)
    {
        _stream = stream;
    }

    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 Read(byte[] pv, int cb, IntPtr pcbRead)
    {
        int read = _stream.Read(pv, 0, cb);
        if (pcbRead != IntPtr.Zero)
        {
            Marshal.WriteInt32(pcbRead, read);
        }
    }

    public void Revert()
    {
        throw new NotImplementedException();
    }

    public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
    {
        long newPos = _stream.Seek(dlibMove, (SeekOrigin)dwOrigin);
        if (plibNewPosition != IntPtr.Zero)
        {
            Marshal.WriteInt64(plibNewPosition, newPos);
        }
    }

    public void SetSize(long libNewSize)
    {
        _stream.SetLength(libNewSize);
    }

    public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
    {
        const int STGTY_STREAM = 2;
        pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
        pstatstg.type = STGTY_STREAM;
        pstatstg.cbSize = _stream.Length;
        pstatstg.grfMode = 0;

        if (_stream.CanRead && _stream.CanWrite)
        {
            const int STGM_READWRITE = 0x00000002;
            pstatstg.grfMode |= STGM_READWRITE;
            return;
        }

        if (_stream.CanRead)
        {
            const int STGM_READ = 0x00000000;
            pstatstg.grfMode |= STGM_READ;
            return;
        }

        if (_stream.CanWrite)
        {
            const int STGM_WRITE = 0x00000001;
            pstatstg.grfMode |= STGM_WRITE;
            return;
        }

        throw new IOException();
    }

    public void UnlockRegion(long libOffset, long cb, int dwLockType)
    {
        throw new NotImplementedException();
    }

    public void Write(byte[] pv, int cb, IntPtr pcbWritten)
    {
        _stream.Write(pv, 0, cb);
        if (pcbWritten != IntPtr.Zero)
        {
            Marshal.WriteInt32(pcbWritten, cb);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样。现在您可以使用这样的代码(使用与此处提供的原始文章中相同的示例: http: //dlaa.me/blog/post/9913083):

new VirtualFileDataObject.FileDescriptor
{
    Name = "Alphabet.txt",
    Length = 26,
    ChangeTimeUtc = DateTime.Now.AddDays(-1),
    StreamContents = () =>
    {
        var contents = Enumerable.Range('a', 26).Select(i => (byte)i).ToArray();
        MemoryStream ms = new MemoryStream(contents); // don't dispose/using here, it would be too early
        return ms;
    }
};
Run Code Online (Sandbox Code Playgroud)