删除的 zip 文件会导致 e.Data.GetData("FileContents") 引发异常

Posted

技术标签:

【中文标题】删除的 zip 文件会导致 e.Data.GetData("FileContents") 引发异常【英文标题】:Dropped zip file causes e.Data.GetData("FileContents") to throw an exception 【发布时间】:2014-09-19 01:12:07 【问题描述】:

我正在尝试在我的 WPF 应用程序中为从 zip 存档中拖动的文件实现处理程序。处理程序应获取文件内容以进行进一步处理。

我的环境:Windows7,已安装 7-zip,Visual Studio 2012 Express,.Net 4.5

这是一个简单的 MainWindow 应用程序的代码来演示该问题:

public partial class MainWindow : Window

  public MainWindow()
  
    InitializeComponent();
    AllowDrop= true;
    Drop += onDrop;
  

  private void onDrop(object sender, DragEventArgs e)
  
    if (e.Data.GetDataPresent("FileContents"))
    
      var fileContents = e.Data.GetData("FileContents");
      //get file contents...
    
  

当我将 zip 存档中包含的文件拖到我的窗口时,对 e.Data.GetData("FileContents") 的调用会引发带有以下调用堆栈的 System.ArgumentException(“参数超出范围”):

System.Windows.DataObject.OleConverter.GetDataInner(formatetc, medium)  
System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(format, aspect, index) 
System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(format, aspect, index) 
System.Windows.DataObject.OleConverter.GetData(format, autoConvert, aspect, index)  
System.Windows.DataObject.OleConverter.GetData(format, autoConvert) 
System.Windows.DataObject.GetData(format, autoConvert)  
System.Windows.DataObject.GetData(format)   
TestZip.MainWindow.onDrop(sender, e) Zeile 34   C#

我查看了这个 OleConverter (http://reflector.webtropy.com/default.aspx/Dotnetfx_Win7_3@5@1/Dotnetfx_Win7_3@5@1/3@5@1/DEVDIV/depot/DevDiv/releases/Orcas/NetFXw7/wpf/src/Core/CSharp/System/Windows/dataobject@cs/1/dataobject@cs) 的源代码,但 GetDataInner() 方法的实现方式类似于

private void GetDataInner(ref FORMATETC formatetc, out STGMEDIUM medium)
  
     new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); // BlessedAssert
     try
      
             _innerData.GetData(ref formatetc, out medium);
      
     finally
     
         SecurityPermission.RevertAssert();
      
 

所以这也没有提供关于这里有什么问题的更多信息。

我也尝试使用卸载的 7-zip 和不同的 zip 存档,但没有任何变化。

我的问题:有人知道这里出了什么问题吗?我需要做什么才能从拖放到我的窗口中的 zip 存档中获取文件的内容?

【问题讨论】:

用于检索 FileContents 的 D+D 协议是 more elaborate。 WPF 不支持。 这是否意味着,虽然 GetDataPresent("FileContents") 返回 true,但我无法在 WPF 中执行此操作? codeproject.com/Articles/28209/Outlook-Drag-and-Drop-in-C 展示了如何提取 FileContents。请注意,那里的代码示例没有为 64 位正确编写,并且需要修复对 (int) 和 sizeof() 操作的各种强制转换。 【参考方案1】:

老问题,但我今天需要这样做......

使用语句:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;

参考 PresentationCore https://msdn.microsoft.com/en-us/library/system.windows.idataobject(v=vs.110).aspx

首先您需要获取“FileDescriptor”内容,这是一个读取它们的类。

/// <summary>
/// Specifies which fields are valid in a FileDescriptor Structure
/// </summary>    
[Flags]
enum FileDescriptorFlags : uint

    ClsId = 0x00000001,
    SizePoint = 0x00000002,
    Attributes = 0x00000004,
    CreateTime = 0x00000008,
    AccessTime = 0x00000010,
    WritesTime = 0x00000020,
    FileSize = 0x00000040,
    ProgressUI = 0x00004000,
    LinkUI = 0x00008000,
    Unicode = 0x80000000,


internal static class FileDescriptorReader
        
    internal sealed class FileDescriptor
    
        public FileDescriptorFlags Flagsget;set;
        public Guid ClassIdget;set;
        public Size Sizeget;set;
        public Point Pointget;set;
        public FileAttributes FileAttributesget;set;
        public DateTime CreationTimeget;set;
        public DateTime LastAccessTimeget;set;
        public DateTime LastWriteTimeget;set;
        public Int64 FileSizeget;set;
        public string FileNameget;set;

        public FileDescriptor(BinaryReader reader)
        
            //Flags
            Flags = (FileDescriptorFlags)reader.ReadUInt32();
            //ClassID
            ClassId = new Guid(reader.ReadBytes(16));
            //Size
            Size = new Size(reader.ReadInt32(), reader.ReadInt32());
            //Point
            Point = new Point(reader.ReadInt32(), reader.ReadInt32());
            //FileAttributes
            FileAttributes = (FileAttributes)reader.ReadUInt32();
            //CreationTime
            CreationTime = new DateTime(1601,1,1).AddTicks(reader.ReadInt64());
            //LastAccessTime
            LastAccessTime = new DateTime(1601,1,1).AddTicks(reader.ReadInt64());
            //LastWriteTime
            LastWriteTime = new DateTime(1601, 1, 1).AddTicks(reader.ReadInt64());
            //FileSize
            FileSize = reader.ReadInt64();
            //FileName
            byte[] nameBytes = reader.ReadBytes(520);
            int i = 0; 
            while(i < nameBytes.Length)
            
                if (nameBytes[i] == 0 && nameBytes[i + 1] == 0)
                    break;
                i++;
                i++;
            
            FileName = UnicodeEncoding.Unicode.GetString(nameBytes, 0, i);
        
    

    public static IEnumerable<FileDescriptor> Read(Stream fileDescriptorStream)
    
        BinaryReader reader = new BinaryReader(fileDescriptorStream);
        var count = reader.ReadUInt32();
        while (count > 0)
        
            FileDescriptor descriptor = new FileDescriptor(reader);

            yield return descriptor;

            count--;
        
    

    public static IEnumerable<string> ReadFileNames(Stream fileDescriptorStream)
    
        BinaryReader reader = new BinaryReader(fileDescriptorStream);
        var count = reader.ReadUInt32();
        while(count > 0)
        
            FileDescriptor descriptor = new FileDescriptor(reader);

            yield return descriptor.FileName;

            count--;
        
    

现在使用它您可以获得每个文件的匹配文件内容:

static class ClipboardHelper

    internal static MemoryStream GetFileContents(System.Windows.IDataObject dataObject, int index)
    
        //cast the default IDataObject to a com IDataObject
        IDataObject comDataObject;
        comDataObject = (IDataObject)dataObject;

        System.Windows.DataFormat Format = System.Windows.DataFormats.GetDataFormat("FileContents");
        if (Format == null)
            return null;

        FORMATETC formatetc = new FORMATETC();
        formatetc.cfFormat = (short)Format.Id;
        formatetc.dwAspect = DVASPECT.DVASPECT_CONTENT;
        formatetc.lindex = index;
        formatetc.tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_HGLOBAL;


        //create STGMEDIUM to output request results into
        STGMEDIUM medium = new STGMEDIUM();

        //using the com IDataObject interface get the data using the defined FORMATETC
        comDataObject.GetData(ref formatetc, out medium);

        switch (medium.tymed)
        
            case TYMED.TYMED_ISTREAM: return GetIStream(medium);
            default: throw new NotSupportedException();
        
    

    private static MemoryStream GetIStream(STGMEDIUM medium)
    
        //marshal the returned pointer to a IStream object
        IStream iStream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
        Marshal.Release(medium.unionmember);

        //get the STATSTG of the IStream to determine how many bytes are in it
        var iStreamStat = new System.Runtime.InteropServices.ComTypes.STATSTG();
        iStream.Stat(out iStreamStat, 0);
        int iStreamSize = (int)iStreamStat.cbSize;

        //read the data from the IStream into a managed byte array
        byte[] iStreamContent = new byte[iStreamSize];
        iStream.Read(iStreamContent, iStreamContent.Length, IntPtr.Zero);

        //wrapped the managed byte array into a memory stream
        return new MemoryStream(iStreamContent);
    

现在您可以枚举文件内容中的流:

                var fileDescriptor = (MemoryStream)Clipboard.GetDataObject().GetData("FileGroupDescriptorW");
                var files = FileDescriptorReader.Read(fileDescriptor);
                var fileIndex = 0;
                    foreach (var fileContentFile in files)
                    
                        if ((fileContentFile.FileAttributes & FileAttributes.Directory) != 0)
                        
                            //Do something with directories?
                            //Note that directories do not have FileContents
                            //And will throw if we try to read them
                        
                        else
                        
                            var fileData = ClipboardHelper.GetFileContents(Clipboard.GetDataObject(), FileIndex);
                            fileData.Position = 0;
                            //Do something with the fileContent Stream
                        
                        fileIndex++;                            
                    

【讨论】:

我尝试使用您的建议。但这似乎是不完整的。此外,它似乎不是您正在使用的实际代码,因为它包含编译错误(缺少“;”)。您能否通过使用语句和类定义的要求来扩展您的解决方案。例如,我与 FORMATETC 和 IDataObject 斗争。 Windows.IDataObject 是 System.Windows.IDataObject,您必须为其添加 PresentationCore 引用。 FORMATETC 来自 System.Runtime.InteropServices.ComTypes。 使用列表:使用系统;使用 System.Collections.Generic;使用 System.Drawing;使用 System.IO;使用 System.Runtime.InteropServices;使用 System.Runtime.InteropServices.ComTypes;使用 System.Text;

以上是关于删除的 zip 文件会导致 e.Data.GetData("FileContents") 引发异常的主要内容,如果未能解决你的问题,请参考以下文章

使用 7-Zip 压缩和删除同一文件

Git 存档在最终 zip 输出中删除 .ebextensions

压缩与解压

如何解决java.util.zip.ZipException

文件目录压缩tar zip

Linux -zip-压缩工具