2014-03-27 22 views
3

我成功使用VirtualFileDataObject code from Delay's blog,但我想避免流式传输整个文件到内存中。拖放使用VirtualFileDataObject的IStream的大型虚拟文件

我发现以前在Stack Overflow上回答了这个问题Drag and Drop large virtual files from c# to Windows Explorer问题由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); 
       }, 
      }); 
    } 

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

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

  foreach (var fileDescriptor in fileDescriptors) 
     { 
      **SetData(FILECONTENTS, index, fileDescriptor.StreamContents);** 
      index++; 
     } 

基本上改变“Action streamData”到“Stream stream”导致我的问题。我不确定如何在更改完成后调用它。

所有这些代码来自延迟VirtualFileDataObject。我不知道我是否应该在这里发布它。但是,如果你按照上面的链接,它会带你到博客,所以你可以查看它。

我是如此接近,只是不能走出明白这最后一步,感谢考虑看看

回答

2

我有完全相同的问题。这是我做过什么来解决这个问题(这就像你说的并没有在对方的回答得到了充分的阐述)

1)修改FileDescriptorStreamContents从这个属性:

public Action<Stream> StreamContents { get; set; } 

这样:

public Func<Stream> StreamContents { get; set; } 

(而不是传递Stream客户端可以写,我们将期待一个Stream我们可以从,这正是浏览器是如何工作的以及它希望读)

2)修改从该所述SetData方法重载:

public void SetData(short dataFormat, int index, Action<Stream> streamData) 

这样:

public void SetData(short dataFormat, int index, Func<Stream> streamData) 

3)变化SetData代码的GetData拉姆达这样:

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

4)添加这个ManagedIStream类的代码(你也可以删除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); 
     } 
    } 
} 

就是这样。现在你可以使用这样的代码(使用相同的样品可在这里的原始文章: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; 
    } 
}; 
+0

时/我怎么关闭文件上的流??? –

+0

@DavidRefaeli - 当调用者在另一端(无论谁)关闭流时应该调用Dispose。 –

+0

嗯,是的,但如何?你可以举个例子吗?你在哪里打电话ms.Close(),你如何将它传递给外部?在我的情况下,我使用的是我在临时位置创建的实际文件(返回新的FileStream(sanitizedFile,FileMode.Open,FileAccess.Read);)它使文件句柄保持在临时位置,因此我无法删除它... –