2014-03-27 20 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);)它使文件句柄保持在臨時位置,因此我無法刪除它... –