在内存中从.CAB存档或InfoPath XSN文件中提取的最小C#代码

最近我一直在尝试实现一些从InfoPath XSN文件(.CAB存档)中提取文件的function。 在广泛搜索互联网之后,似乎没有本机.NET API。 所有当前的解决方案都围绕着大型库,即托管的C ++,它包含了Cabinet.dll。

遗憾的是,所有这些都违反了我的公司“没有第三方图书馆”的政策。

从2.0开始,.NET获得了一个名为UnmanagedFunctionPointer的属性,该属性允许使用__cdecl进行源级回调声明。 在此之前,__ stdcall是镇上唯一的节目,除非你不介意捏造IL,这种做法在这里也是非法的。 我立即知道这将允许为Cabinet.dll实现一个相当小的C#包装器,但我无法在任何地方找到一个示例。

有没有人知道使用本机代码执行此操作的方式比下面更清晰?

我当前的解决方案(执行非托管代码,但完全正常,在32/64位上测试):

[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 _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(); _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, "", "", 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 } 

您是否可以使用Microsoft创建的其他库? 它没有框架,但有一个用于处理Cab文件的MS库:

Microsoft.Deployment.Compression.Cab

它可以如下使用

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

我们也有“没有第三方图书馆”的政策(很痛苦)。 然而,出于各种原因,我们需要“展开”IP表格。 我们的解决方案是使用CABARC(这是一个来自Microsoft的exe,它曾经包含在Windows安装中,尽管可能不再存在)。 它可以用shell编写任何语言脚本,我们使用vbs或powershell。 它就像任何带命令行开关的zip / unzip程序一样工作。

对于那些从上面找到好消息的人来说,这是另一种选择

扩展CAB文件与扩展ZIP文件相同。

你可以在这里找到一些代码

这种方法怎么样: 如何从.CAB文件中提取文件? 我的意思是JohnWein (MCC)的提议。 然后你用文件做什么 – 无论你是在目标直接文件中复制它们,如在引用示例的foreach状态中,还是只在内存中使用它们 – 取决于你。 (我想这也是亚历山德鲁 – 丹的意思 – 如果你不是在看问题,而是在那里找到答案。)

如果这种方法在你的场景中有效,那么这是一个更简单的解决方案 – 没有任何第三方dll。

可以?

如果担心第三方二进制库,我无法帮助,但有开源ZIP库将打开一个CAB文件。

http://www.icsharpcode.net/opensource/sharpziplib/