2011-12-16 10 views
12

Ultimamente ho cercato di implementare alcune funzionalità che estrae i file da un file XSN di InfoPath (un archivio .CAB). Dopo lunghe ricerche su Internet, sembra che non ci sia alcuna API .NET nativa per questo. Tutte le soluzioni attuali sono incentrate su librerie di grandi dimensioni, ad esempio C++ gestito che racchiudono Cabinet.dll.Codice minimo C# da estrarre dagli archivi .CAB o dai file InfoPath XSN, nella memoria

Tutto questo, purtroppo, cade in fallo con la mia politica "No biblioteche di terze parti".

A partire da 2.0, .NET ha ottenuto un attributo denominato UnmanagedFunctionPointer che consente dichiarazioni di callback a livello di origine utilizzando __cdecl. Prima di questo, __stdcall era l'unico spettacolo in città, a meno che non ti dispiacesse fondere l'IL, una pratica anche messa al bando qui. Sapevo immediatamente che ciò avrebbe consentito l'implementazione di un wrapper C# piuttosto piccolo per Cabinet.dll, ma non sono riuscito a trovare un esempio di uno ovunque.

Qualcuno sa di un modo più pulito rispetto al seguito di fare questo con il codice nativo?

mia soluzione attuale (esegue il codice non gestito, ma perfettamente funzionante, testato su 32/64-bit):

[StructLayout(LayoutKind.Sequential)] 
public class CabinetInfo //Cabinet API: "FDCABINETINFO" 
{ 
    public int cbCabinet; 
    public short cFolders; 
    public short cFiles; 
    public short setID; 
    public short iCabinet; 
    public int fReserve; 
    public int hasprev; 
    public int hasnext; 
} 

public class CabExtract : IDisposable 
{ 
    //If any of these classes end up with a different size to its C equivilent, we end up with crash and burn. 
    [StructLayout(LayoutKind.Sequential)] 
    private class CabError //Cabinet API: "ERF" 
    { 
     public int erfOper; 
     public int erfType; 
     public int fError; 
    } 

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 
    private class FdiNotification //Cabinet API: "FDINOTIFICATION" 
    { 
     public int cb; 
     public string psz1; 
     public string psz2; 
     public string psz3; 
     public IntPtr userData; 
     public IntPtr hf; 
     public short date; 
     public short time; 
     public short attribs; 
     public short setID; 
     public short iCabinet; 
     public short iFolder; 
     public int fdie; 
    } 

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

    private class DecompressFile 
    { 
     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; } 
    } 

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

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

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

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

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

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

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

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

    [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDICreate", CharSet = CharSet.Ansi)] 
    private 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 = "FDIIsCabinet", CharSet = CharSet.Ansi)] 
    private static extern bool FdiIsCabinet(
     IntPtr hfdi, 
     IntPtr hf, 
     [MarshalAs(UnmanagedType.LPStruct)] CabinetInfo cabInfo); 

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

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

    private readonly FdiFileCloseDelegate _fileCloseDelegate; 
    private readonly FdiFileOpenDelegate _fileOpenDelegate; 
    private readonly FdiFileReadDelegate _fileReadDelegate; 
    private readonly FdiFileSeekDelegate _fileSeekDelegate; 
    private readonly FdiFileWriteDelegate _fileWriteDelegate; 
    private readonly FdiMemAllocDelegate _femAllocDelegate; 
    private readonly FdiMemFreeDelegate _memFreeDelegate; 

    private readonly CabError _erf; 
    private readonly List<DecompressFile> _decompressFiles; 
    private readonly byte[] _inputData; 
    private IntPtr _hfdi; 
    private bool _disposed; 
    private const int CpuTypeUnknown = -1; 

    public CabExtract(byte[] inputData) 
    { 
     _fileReadDelegate = FileRead; 
     _fileOpenDelegate = InputFileOpen; 
     _femAllocDelegate = MemAlloc; 
     _fileSeekDelegate = FileSeek; 
     _memFreeDelegate = MemFree; 
     _fileWriteDelegate = FileWrite; 
     _fileCloseDelegate = InputFileClose; 
     _inputData = inputData; 
     _decompressFiles = new List<DecompressFile>(); 
     _erf = new CabError(); 
     _hfdi = IntPtr.Zero; 
    } 

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

    private static bool FdiCopy(
     IntPtr hfdi, 
     FdiNotifyDelegate fnNotify) 
    { 
     return FdiCopy(hfdi, "<notused>", "<notused>", 0, fnNotify, IntPtr.Zero, IntPtr.Zero); 
    } 

    private IntPtr FdiContext 
    { 
     get 
     { 
      if (_hfdi == IntPtr.Zero) 
      { 
       _hfdi = FdiCreate(_femAllocDelegate, _memFreeDelegate, _fileOpenDelegate, _fileReadDelegate, _fileWriteDelegate, _fileCloseDelegate, _fileSeekDelegate, _erf); 
       if (_hfdi == IntPtr.Zero) 
        throw new ApplicationException("Failed to create FDI context."); 
      } 
      return _hfdi; 
     } 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
    } 

    private void Dispose(bool disposing) 
    { 
     if (!_disposed) 
     { 
      if (_hfdi != IntPtr.Zero) 
      { 
       FdiDestroy(_hfdi); 
       _hfdi = IntPtr.Zero; 
      } 
      _disposed = true; 
     } 
    } 

    private IntPtr NotifyCallback(FdiNotificationType fdint, FdiNotification fdin) 
    { 
     switch (fdint) 
     { 
      case FdiNotificationType.CopyFile: 
       return OutputFileOpen(fdin); 
      case FdiNotificationType.CloseFileInfo: 
       return OutputFileClose(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; 
    } 

    private IntPtr OutputFileOpen(FdiNotification fdin) 
    { 
     var extractFile = _decompressFiles.Where(ef => ef.Name == fdin.psz1).SingleOrDefault(); 

     if (extractFile != null) 
     { 
      var stream = new MemoryStream(); 
      GCHandle gch = GCHandle.Alloc(stream); 
      extractFile.Handle = (IntPtr)gch; 
      return extractFile.Handle; 
     } 

     //Don't extract 
     return IntPtr.Zero; 
    } 

    private IntPtr OutputFileClose(FdiNotification fdin) 
    { 
     var extractFile = _decompressFiles.Where(ef => ef.Handle == fdin.hf).Single(); 
     var stream = StreamFromHandle(fdin.hf); 

     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 int FileRead(IntPtr hf, byte[] buffer, int cb) 
    { 
     var stream = StreamFromHandle(hf); 
     return stream.Read(buffer, 0, cb); 
    } 

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

    public bool ExtractFile(string fileName, out byte[] outputData, out int outputLength) 
    { 
     if (_disposed) 
      throw new ObjectDisposedException("CabExtract"); 

     var fileToDecompress = new DecompressFile(); 
     fileToDecompress.Found = false; 
     fileToDecompress.Name = fileName; 

     _decompressFiles.Add(fileToDecompress); 

     FdiCopy(FdiContext, NotifyCallback); 

     if (fileToDecompress.Found) 
     { 
      outputData = fileToDecompress.Data; 
      outputLength = fileToDecompress.Length; 
      _decompressFiles.Remove(fileToDecompress); 
      return true; 
     } 

     outputData = null; 
     outputLength = 0; 
     return false; 
    } 

    public bool IsCabinetFile(out CabinetInfo cabinfo) 
    { 
     if (_disposed) 
      throw new ObjectDisposedException("CabExtract"); 

     var stream = new MemoryStream(_inputData); 
     GCHandle gch = GCHandle.Alloc(stream); 

     try 
     { 
      var info = new CabinetInfo(); 
      var ret = FdiIsCabinet(FdiContext, (IntPtr)gch, info); 
      cabinfo = info; 
      return ret; 
     } 
     finally 
     { 
      stream.Close(); 
      gch.Free(); 
     } 
    } 

    public static bool IsCabinetFile(byte[] inputData, out CabinetInfo cabinfo) 
    { 
     using (var decomp = new CabExtract(inputData)) 
     { 
      return decomp.IsCabinetFile(out cabinfo); 
     } 
    } 

    //In an ideal world, this would take a stream, but Cabinet.dll seems to want to open the input several times. 
    public static bool ExtractFile(byte[] inputData, string fileName, out byte[] outputData, out int length) 
    { 
     using (var decomp = new CabExtract(inputData)) 
     { 
      return decomp.ExtractFile(fileName, out outputData, out length); 
     } 
    } 

    //TODO: Add methods for enumerating/extracting multiple files 
} 
+0

interessante. Se questo è un codice funzionante, piuttosto che una domanda (che tu indichi che lo è), considera di metterlo come un (piccolo) "progetto" su [CodePlex] (http://codeplex.com) o [github] (http: //github.com), preferibilmente con alcuni test. –

+0

Pensato, ma quello che volevo veramente era un piccolo lavoro "Copia/Incolla" da StackOverflow, non un progetto, quindi questo è quello che sto restituendo –

+0

Abbastanza giusto e apprezzato, tuttavia potresti ottenere un "non una vera domanda" chiudi questo. SO è un tipo di sito Q/A, quindi puoi scrivere una domanda (fasulla) e fornire la tua risposta. Personalmente, non mi preoccupo, altri possono. –

risposta

0

Abbiamo anche il "no terze librerie parti" della politica (il dolore che è). Eppure abbiamo bisogno di "srotolare" i moduli IP per vari motivi. La nostra soluzione era l'uso di CABARC (che è un exe ma di Microsoft, e che era incluso nell'installazione di Windows anche se potrebbe non essere più disponibile). Può essere copiato in qualsiasi lingua da una shell, usiamo VBS o PowerShell. Funziona proprio come qualsiasi programma zip/decomprimere con le opzioni della riga di comando.

Solo un'altra opzione per coloro che trovano le buone informazioni dall'alto.

+0

Interessante, ma che verrà estratto nel file system; non in memoria. – vcsjones

0

L'estensione dei file CAB è identica all'espansione dei file ZIP.

È possibile trovare un codice here

+0

Lo snip che hai postato utilizza java.util.zip.ZipFile. Anche se questo può aprire un file .CAB, non è una soluzione che gira su un sistema senza librerie di terze parti installate, che sconfigge l'obiettivo originale. –

0

Che dire di questo approccio: How to extract a file from a .CAB file? voglio dire la proposta di JohnWein (un MCC) là. Cosa si fa allora con i file - sia che li copi in una direttrice di destinazione, come nel statuto foreach dell'esempio di riferimento, o semplicemente li usi in memoria - dipende da te allora. (Credo che questo è ciò che ha significato così Alexandru-Dan - se non stai guardando la domanda, ma la risposta c'è, invece.)

Se questo approccio non lavoro nel vostro scenario, allora si tratta di un molto più soluzione più semplice - senza alcuna dll di terze parti.

Lo fa?

1

Sei in grado di usare altre librerie create da Microsoft? Non viene fornito con il framework ma esiste una libreria MS per lavorare con i file Cab:

Microsoft.Deployment.Compression.Cab

Può essere utilizzato come segue

CabInfo cab = new CabInfo(@"C:\data.cab"); 
cab.Unpack(@"C:\ExtractDir"); 
Problemi correlati