2013-06-16 4 views
2

Я разрабатываю приложение C#, и мне нужно извлечь файл кабины.Извлечь файл .cab в C#

Я не смог найти библиотеку, которая делает это на C#) Я не могу использовать Microsoft.Deployment.Compression.Cab.dll из-за проблемы с лицензированием.

Я нашел this код, но проблема в том, что когда я его использую, я могу найти и извлечь только первый файл в шкафу.

OutputFileClose вызывается только в том случае, если OutputFileOpen возвращает что-то либо в IntPtr.Zero. , но если OutputFileClose - это вызовы, то перечисление прекращается. Итак, для этого кода OutputFileClose можно вызывать только для одного файла

Может кто-нибудь, пожалуйста, помогите мне разобраться, как написать код, который будет извлекать все файлы?

+1

В чем проблема лицензирования? –

+0

Это находится под Ms-Rl. – user844541

+0

Вы не можете использовать лицензию, совместимую с MS-RL? –

ответ

5

Я узнал, что Microsoft.Deployment.Compression.cab DLL также могут быть получены из here

если вы посмотрите на предыдущие версии, такие как version 3.5, вы увидите, что они были лицензированы с Common Public License Version 1.0 (CPL).

Похоже, что только в более поздних версиях лицензия была изменена на MS-RL.

Я также смог создать собственное решение, но оно не является оптимальным (я прекратил работать с ним, так как обнаружил, что могу использовать Microsoft.Deployment.Compression.cab).

Это код:

public class CabExtractor : IDisposable 
{ 
    private static class NativeMethods 
    { 
     [StructLayout(LayoutKind.Sequential)] 
     internal class CabError //Cabinet API: "ERF" 
     { 
      public int erfOper; 
      public int erfType; 
      public int fError; 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     internal class FdiNotification //Cabinet API: "FDINOTIFICATION" 
     { 
      internal int cb; 
      //not sure if this should be a IntPtr or a strong 
      internal IntPtr psz1; 
      internal IntPtr psz2; 
      internal IntPtr psz3; 
      internal IntPtr pv; 
      internal IntPtr hf; 
      internal short date; 
      internal short time; 
      internal short attribs; 
      internal short setID; 
      internal short iCabinet; 
      internal short iFolder; 
      internal int fdie; 

     } 

     internal enum FdiNotificationType 
     { 
      CabinetInfo, 
      PartialFile, 
      CopyFile, 
      CloseFileInfo, 
      NextCabinet, 
      Enumerate 
     } 


     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate IntPtr FdiMemAllocDelegate(int numBytes); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate void FdiMemFreeDelegate(IntPtr mem); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate IntPtr FdiFileOpenDelegate(string fileName, int oflag, int pmode); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate Int32 FdiFileReadDelegate(IntPtr hf, 
                [In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2, 
                 ArraySubType = UnmanagedType.U1)] byte[] buffer, int cb); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate Int32 FdiFileWriteDelegate(IntPtr hf, 
                [In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2, 
                 ArraySubType = UnmanagedType.U1)] byte[] buffer, int cb); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate Int32 FdiFileCloseDelegate(IntPtr hf); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate Int32 FdiFileSeekDelegate(IntPtr hf, int dist, int seektype); 

     [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
     internal delegate IntPtr FdiNotifyDelegate(
      FdiNotificationType fdint, [In] [MarshalAs(UnmanagedType.LPStruct)] FdiNotification fdin); 

     [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDICreate", CharSet = CharSet.Ansi)] 
     internal static extern IntPtr FdiCreate(
      FdiMemAllocDelegate fnMemAlloc, 
      FdiMemFreeDelegate fnMemFree, 
      FdiFileOpenDelegate fnFileOpen, 
      FdiFileReadDelegate fnFileRead, 
      FdiFileWriteDelegate fnFileWrite, 
      FdiFileCloseDelegate fnFileClose, 
      FdiFileSeekDelegate fnFileSeek, 
      int cpuType, 
      [MarshalAs(UnmanagedType.LPStruct)] CabError erf); 

     [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi)] 
     internal static extern bool FdiDestroy(IntPtr hfdi); 

     [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDICopy", CharSet = CharSet.Ansi)] 
     internal static extern bool FdiCopy(
      IntPtr hfdi, 
      string cabinetName, 
      string cabinetPath, 
      int flags, 
      FdiNotifyDelegate fnNotify, 
      IntPtr fnDecrypt, 
      IntPtr userData); 
    } 

    internal class ArchiveFile 
    { 
     public IntPtr Handle { get; set; } 
     public string Name { get; set; } 
     public bool Found { get; set; } 
     public int Length { get; set; } 
     public byte[] Data { get; set; } 
    } 

    #region fields and properties 

    /// Very important! 
    /// Do not try to call directly to this methods, instead use the delegates. if you use them directly it may cause application crashes, corruption and data loss. 
    /// Using fields to save the delegate so that the delegate won't be garbage collected ! 
    /// When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called. 
    private readonly NativeMethods.FdiMemAllocDelegate _fdiAllocMemHandler; 
    private readonly NativeMethods.FdiMemFreeDelegate _fdiFreeMemHandler; 
    private readonly NativeMethods.FdiFileOpenDelegate _fdiOpenStreamHandler; 
    private readonly NativeMethods.FdiFileReadDelegate _fdiReadStreamHandler; 
    private readonly NativeMethods.FdiFileWriteDelegate _fdiWriteStreamHandler; 
    private readonly NativeMethods.FdiFileCloseDelegate _fdiCloseStreamHandler; 
    private readonly NativeMethods.FdiFileSeekDelegate _fdiSeekStreamHandler; 

    private ArchiveFile _currentFileToDecompress; 
    readonly List<string> _fileNames = new List<string>(); 
    private readonly NativeMethods.CabError _erf; 
    private const int CpuTypeUnknown = -1; 
    private readonly byte[] _inputData; 
    private bool _disposed; 
    /// <summary> 
    /// 
    /// </summary> 
    private readonly List<string> _subDirectoryToIgnore = new List<string>(); 
    /// <summary> 
    /// Path to the folder where the files will be extracted to 
    /// </summary> 
    private readonly string _extractionFolderPath; 
    /// <summary> 
    /// The name of the folder where the files will be extracted to 
    /// </summary> 
    public const string ExtractedFolderName = "ExtractedFiles"; 

    public const string CabFileName = "setup.cab"; 

    #endregion 

    public CabExtractor(string cabFilePath, IEnumerable<string> subDirectoryToUnpack) 
     : this(cabFilePath) 
    { 
     if (subDirectoryToUnpack != null) 
      _subDirectoryToIgnore.AddRange(subDirectoryToUnpack); 
    } 
    public CabExtractor(string cabFilePath) 
    { 
     var cabBytes = 
      File.ReadAllBytes(cabFilePath); 
     _inputData = cabBytes; 
     var cabFileLocation = Path.GetDirectoryName(cabFilePath) ?? ""; 
     _extractionFolderPath = Path.Combine(cabFileLocation, ExtractedFolderName); 
     _erf = new NativeMethods.CabError(); 
     FdiContext = IntPtr.Zero; 

     _fdiAllocMemHandler = MemAlloc; 
     _fdiFreeMemHandler = MemFree; 
     _fdiOpenStreamHandler = InputFileOpen; 
     _fdiReadStreamHandler = FileRead; 
     _fdiWriteStreamHandler = FileWrite; 
     _fdiCloseStreamHandler = InputFileClose; 
     _fdiSeekStreamHandler = FileSeek; 

     FdiContext = FdiCreate(_fdiAllocMemHandler, _fdiFreeMemHandler, _fdiOpenStreamHandler, _fdiReadStreamHandler, _fdiWriteStreamHandler, _fdiCloseStreamHandler, _fdiSeekStreamHandler, _erf); 


    } 







    public bool ExtractCabFiles() 
    { 
     if (!FdiIterate()) 
     { 
      throw new Exception("Failed to iterate cab files"); 
     } 

     foreach (var file in _fileNames) 
     { 
      ExtractFile(file); 
     } 
     return true; 
    } 

    private void ExtractFile(string fileName) 
    { 
     try 
     { 
      _currentFileToDecompress = new ArchiveFile { Name = fileName }; 
      FdiCopy(); 
      CreateAllRelevantDirectories(fileName); 
      if (_currentFileToDecompress.Data != null) 
      { 
       File.WriteAllBytes(Path.Combine(_extractionFolderPath, _currentFileToDecompress.Name), _currentFileToDecompress.Data); 
      } 
     } 
     catch (Exception ex) 
     { 

      SbaLogger.Instance.Error(ex); 
      SbaLogger.Instance.Error(string.Format("Failed to cextract file file {0}", fileName)); 
     } 

    } 

    private void CreateAllRelevantDirectories(string filePath) 
    { 
     try 
     { 
      if (!Directory.Exists(_extractionFolderPath)) 
      { 
       Directory.CreateDirectory(_extractionFolderPath); 
      } 
      var fullPathToFile = Path.GetDirectoryName(filePath); 
      if (fullPathToFile != null && 
       !Directory.Exists(Path.Combine(_extractionFolderPath, fullPathToFile))) 
      { 
       Directory.CreateDirectory(Path.Combine(_extractionFolderPath, fullPathToFile)); 
      } 
     } 
     catch (Exception ex) 
     { 
      SbaLogger.Instance.Error(ex); 
      SbaLogger.Instance.Error(string.Format("Failed to create directories for the file {0}",filePath)); 
     } 

    } 



    private static string GetFileName(NativeMethods.FdiNotification notification) 
    { 
     var encoding = ((int)notification.attribs & 128) != 0 ? Encoding.UTF8 : Encoding.Default; 
     int length = 0; 
     while (Marshal.ReadByte(notification.psz1, length) != 0) 
      checked { ++length; } 
     var numArray = new byte[length]; 
     Marshal.Copy(notification.psz1, numArray, 0, length); 
     string path = encoding.GetString(numArray); 
     if (Path.IsPathRooted(path)) 
      path = path.Replace(String.Concat(Path.VolumeSeparatorChar), ""); 
     return path; 
    } 
    private IntPtr ExtractCallback(NativeMethods.FdiNotificationType fdint, NativeMethods.FdiNotification fdin) 
    { 
     switch (fdint) 
     { 
      case NativeMethods.FdiNotificationType.CopyFile: 
       return CopyFiles(fdin); 
      case NativeMethods.FdiNotificationType.CloseFileInfo: 
       return OutputFileClose(fdin); 
      default: 
       return IntPtr.Zero; 
     } 
    } 

    private IntPtr IterateCallback(NativeMethods.FdiNotificationType fdint, NativeMethods.FdiNotification fdin) 
    { 
     switch (fdint) 
     { 
      case NativeMethods.FdiNotificationType.CopyFile: 
       return OutputFileOpen(fdin); 
      default: 
       return IntPtr.Zero; 
     } 
    } 

    private IntPtr InputFileOpen(string fileName, int oflag, int pmode) 
    { 
     var stream = new MemoryStream(_inputData); 
     GCHandle gch = GCHandle.Alloc(stream); 
     return (IntPtr)gch; 
    } 

    private int InputFileClose(IntPtr hf) 
    { 
     var stream = StreamFromHandle(hf); 
     stream.Close(); 
     ((GCHandle)(hf)).Free(); 
     return 0; 
    } 
    /// <summary> 
    /// Copies the contents of input to output. Doesn't close either stream. 
    /// </summary> 
    public static void CopyStream(Stream input, Stream output) 
    { 
     var buffer = new byte[8 * 1024]; 
     int len; 
     while ((len = input.Read(buffer, 0, buffer.Length)) > 0) 
     { 
      output.Write(buffer, 0, len); 
     } 
    } 


    private IntPtr CopyFiles(NativeMethods.FdiNotification fdin) 
    { 
     var fileName = GetFileName(fdin); 
     var extractFile = _currentFileToDecompress.Name == fileName ? _currentFileToDecompress : null; 
     if (extractFile != null) 
     { 
      var stream = new MemoryStream(); 
      GCHandle gch = GCHandle.Alloc(stream); 
      extractFile.Handle = (IntPtr)gch; 
      return extractFile.Handle; 
     } 

     //Do not extract this file 
     return IntPtr.Zero; 
    } 
    private IntPtr OutputFileOpen(NativeMethods.FdiNotification fdin) 
    { 
     try 
     { 
      var extractFile = new ArchiveFile { Name = GetFileName(fdin) }; 
      if (ShouldIgnoreFile(extractFile)) 
      { 
       //ignore this file. 
       return IntPtr.Zero; 
      } 
      var stream = new MemoryStream(); 
      GCHandle gch = GCHandle.Alloc(stream); 
      extractFile.Handle = (IntPtr)gch; 

      AddToListOfFiles(extractFile); 


     } 
     catch (Exception ex) 
     { 
      SbaLogger.Instance.Verbose(ex); 
     } 
     //return IntPtr.Zero so that the iteration will keep on going 
     return IntPtr.Zero; 

    } 

    private bool ShouldIgnoreFile(ArchiveFile extractFile) 
    { 
     var rootFolder = GetFileRootFolder(extractFile.Name); 
     return _subDirectoryToIgnore.Any(dir => dir.Equals(rootFolder, StringComparison.InvariantCultureIgnoreCase)); 
    } 

    private string GetFileRootFolder(string path) 
    { 
     try 
     { 
      return path.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); 
     } 
     catch (Exception) 
     { 

      return string.Empty; 
     } 

    } 

    private void AddToListOfFiles(ArchiveFile extractFile) 
    { 
     if (!_fileNames.Any(file => file.Equals(extractFile.Name))) 
     { 
      _fileNames.Add(extractFile.Name); 
     } 
    } 

    private IntPtr OutputFileClose(NativeMethods.FdiNotification fdin) 
    { 
     var extractFile = _currentFileToDecompress.Handle == fdin.hf ? _currentFileToDecompress : null; 
     var stream = StreamFromHandle(fdin.hf); 

     if (extractFile != null) 
     { 
      extractFile.Found = true; 
      extractFile.Length = (int)stream.Length; 

      if (stream.Length > 0) 
      { 
       extractFile.Data = new byte[stream.Length]; 
       stream.Position = 0; 
       stream.Read(extractFile.Data, 0, (int)stream.Length); 
      } 
     } 

     stream.Close(); 
     return IntPtr.Zero; 
    } 

    private static IntPtr FdiCreate(
    NativeMethods.FdiMemAllocDelegate fnMemAlloc, 
    NativeMethods.FdiMemFreeDelegate fnMemFree, 
    NativeMethods.FdiFileOpenDelegate fnFileOpen, 
    NativeMethods.FdiFileReadDelegate fnFileRead, 
    NativeMethods.FdiFileWriteDelegate fnFileWrite, 
    NativeMethods.FdiFileCloseDelegate fnFileClose, 
    NativeMethods.FdiFileSeekDelegate fnFileSeek, 
    NativeMethods.CabError erf) 
    { 
     return NativeMethods.FdiCreate(fnMemAlloc, fnMemFree, fnFileOpen, fnFileRead, fnFileWrite, 
         fnFileClose, fnFileSeek, CpuTypeUnknown, erf); 
    } 

    private static int FileRead(IntPtr hf, byte[] buffer, int cb) 
    { 
     var stream = StreamFromHandle(hf); 
     return stream.Read(buffer, 0, cb); 
    } 

    private static int FileWrite(IntPtr hf, byte[] buffer, int cb) 
    { 
     var stream = StreamFromHandle(hf); 
     stream.Write(buffer, 0, cb); 
     return cb; 
    } 

    private static Stream StreamFromHandle(IntPtr hf) 
    { 
     return (Stream)((GCHandle)hf).Target; 
    } 

    private IntPtr MemAlloc(int cb) 
    { 
     return Marshal.AllocHGlobal(cb); 
    } 

    private void MemFree(IntPtr mem) 
    { 
     Marshal.FreeHGlobal(mem); 
    } 

    private int FileSeek(IntPtr hf, int dist, int seektype) 
    { 
     var stream = StreamFromHandle(hf); 
     return (int)stream.Seek(dist, (SeekOrigin)seektype); 
    } 

    private bool FdiCopy() 
    { 
     try 
     { 
      return NativeMethods.FdiCopy(FdiContext, "<notused>", "<notused>", 0, ExtractCallback, IntPtr.Zero, IntPtr.Zero); 
     } 
     catch (Exception) 
     { 

      return false; 
     } 

    } 

    private bool FdiIterate() 
    { 
     return NativeMethods.FdiCopy(FdiContext, "<notused>", "<notused>", 0, IterateCallback, IntPtr.Zero, IntPtr.Zero); 
    } 



    private IntPtr FdiContext { get; set; } 

    public void Dispose() 
    { 
     Dispose(true); 
    } 
    private void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      if (!_disposed) 
      { 
       if (FdiContext != IntPtr.Zero) 
       { 
        NativeMethods.FdiDestroy(FdiContext); 
        FdiContext = IntPtr.Zero; 
       } 
       _disposed = true; 
      } 
     } 
    } 
} 
0

Утечка памяти в принятом ответе. метод: частный IntPtr OutputFileClose (NativeMethods.FdiNotification fdin)

следует назвать: GCHandle.FromIntPtr (fdin.hf) .Free();