5

FTPクライアントとして機能するセクションを備えたC#WPFアプリケーションがあり、リモートサーバー上のファイルを一覧表示し、ユーザーがそれらをダウンロードできるようにします。ユーザーがファイル リストから自分のマシン (つまり、Windows Explorer シェル) にファイルをドラッグ アンド ドロップできるようにしたいと考えています。

これを実現するために、Delay のブログ の VirtualFileDataObject コードを使用しAction<Stream>SetData. これは、小さなファイルでうまく機能します。

私の問題は、私が扱っているファイルのいくつかが非常に大きい (2 GB 以上) であり、VirtualFileDataObjectクラスがストリームを処理する方法には、全体をメモリに読み込むことが含まれているため、「十分なストレージがありません」というエラーがスローされる可能性があることです。それらの非常に大きなファイルの場合。

VirtualFileDataObjectコードの関連セクションを以下に示します。ストリーム全体がメモリ内にある必要がないように、このコードを書き直すにはどうすればよいですか?

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

特に、次のセクションがGetData原因です。

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

streamDataAction<stream>、実際のファイル データをストリームに書き込む I が提供するものです。私のデリゲートは、ファイルを開いて、提供されたストリームにバイトを読み取っているだけです。

この最後のステップを回避する方法はありますか? ファイル ストリームを直接渡して Explorer シェルから読み取る方法はありますか? 私がiStream持っている.NETファイルストリームへのポインターに置き換えるようなことを考えています...しかし、それを行うための構文を知るには、COM相互運用について十分に知りません。ヒント/方向性をいただければ幸いです。

4

2 に答える 2

3

さらにグーグルでつまずき、いろいろなことを試した後、私はうまくいくものを手に入れましたが、私はまだより良い解決策を受け入れています. 今のところ、ドロップ操作が発生すると、ファイルを一時的な場所に取得してから、その場所へのファイルSHCreateStreamOnFileExを開くために使用しIStreamています。改訂された部分であるGetDataラムダは次のとおりです。

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

私が言うように、これが最善の方法なのか、それとももっときれいに管理できるのかはわかりませんが、これはうまくいくようです.

于 2012-09-13T19:25:46.950 に答える
2

私は同じ問題を抱えていますが、簡単に修正できます;)

問題は、新しいメモリ ストリームを作成しようとしていることですが、既にメモリ ストリームがあるため必要ありません。IStream を実装するストリーム ラッパーを C# で作成できます。

    /// <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();
   }
}

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

これで、新しいメモリ ストリームは作成されません。

詳細については、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と私のコメントを読んでください

于 2014-02-06T10:57:45.780 に答える