65.9K
CodeProject 正在变化。 阅读更多。
Home

7-Zip 压缩 DLL 的 C# (.NET) 接口

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (61投票s)

2008年6月20日

MIT

6分钟阅读

viewsIcon

658764

downloadIcon

25427

7-Zip 压缩格式 DLL 的一些接口翻译。

关于 7-Zip

7-Zip 是一个开源的压缩程序,具有插件接口。可以通过 DLL 添加新的压缩格式和/或压缩编解码器。7-Zip 预装了多种压缩格式。

  • 7z — 其自有格式具有良好的压缩性能(LZMA、PPMd),但在打包/解包速度方面可能较慢。
  • 打包/解包:ZIP、GZIP、BZIP2 和 TAR
  • 仅解包:RAR、CAB、ISO、ARJ、LZH、CHM、Z、CPIO、RPM、DEB 和 NSIS

该项目是用 C++ 语言编写的。

您可以在官方 7-Zip 网站上找到更多信息 — 此处

关于此贡献

此贡献允许您在用 .NET 语言编写的程序中使用 7-Zip 压缩格式 DLL。

我为我自己的项目创建了这个模块,以便能够处理压缩文件。目前我的项目只有解压功能,所以 7-Zip 接口的这部分才被翻译成了 C#。我计划稍后也翻译压缩功能。现在,如果您需要这样的功能,可以自己实现,使用此代码和 7-Zip 的源代码。

此翻译已进行测试,并在我自己的项目中正常运行。

实现细节

与压缩 DLL 的所有通信都通过类 COM 接口(为什么是类 COM 而不是 COM,可以在已知问题部分看到)。回调也实现为接口。

每个 DLL 都包含一个可以实现一个或多个接口的类。某些格式只允许解压,有些还提供压缩功能。公共接口已翻译成 C#。

  • IProgress - 基本进度回调
  • IArchiveOpenCallback - 压缩文件打开回调
  • ICryptoGetTextPassword - 用于提示输入压缩文件密码的回调
  • IArchiveExtractCallback - 从压缩文件中提取文件的回调
  • IArchiveOpenVolumeCallback - 打开附加压缩文件卷的回调
  • ISequentialInStream - 简单的只读流接口
  • ISequentialOutStream - 简单的只写流接口
  • IInStream - 具有 Seek 功能的输入流接口
  • IOutStream - 输出流接口
  • IInArchive - 主要压缩接口

每个 DLL 导出的函数用于创建压缩类处理器和函数,以获取压缩格式的属性。这些函数在 .NET 中被翻译为委托。

  • CreateObject - 创建具有给定类 ID 的对象。主要用于创建 IInArchive 实例。
  • GetHandlerProperty - 获取压缩格式描述(已实现的类 ID、默认压缩扩展名等)

更新(1.3):在 7-Zip 4.45 版本中,DLL 接口发生了一些变化。现在所有的压缩格式和压缩编解码器都实现为一个大的 DLL。因此,添加了一些新的导出函数(以及翻译中对应的委托)来处理一个 DLL 中的多个压缩处理器类。

关注点

7-Zip 接口使用变体(PropVariant)作为属性值。C# 不支持此类变体作为类,所有此类参数在 C# 中实现为 IntPtr。这是为了兼容性,并且我更喜欢在我的项目中不使用不安全代码。

幸运的是,托管类 System.Runtime.InteropServices.Marshal 有一个名为 GetObjectForNativeVariant 的方法,您可以使用它将此类“指针”转换为对象。但是,此方法并不处理所有 PropVariant 类型(例如 VT_FILETIME),因此对于这些情况,我在此翻译中添加了我自己的 GetObjectForNativeVariant 方法。

7-Zip 通过其自己的接口处理文件,所以如果您想打开磁盘上的文件或内存中的文件,您需要提供实现一个或多个必需接口的类。此翻译中也包含了一些这样的包装类(它们封装了标准的 .NET Stream 类)。

更新(1.2):与 PropVariant 处理相关的复杂性大部分已隐藏在特殊的 PropVariant 结构中。现在接口方法返回 PropVariant 而不是 IntPtr

已知问题

**第一个**也是最令人失望的问题是,您不能直接使用 7-Zip DLL。这意味着您不能简单地从 7-Zip 发行版中获取这些 DLL 并直接在您的项目中使用。这是因为 7-Zip 代码中对 COM 接口的实现不完整。所有问题都与 IUnknown.QueryInterface 实现有关。7-Zip 的 QueryInterface 在被查询时不会返回 IUnknown 接口(这对于在 .NET 中使用 COM 接口来说是最关键的部分),并且有些类根本不返回任何接口!

这是因为 7-Zip 代码是 C++ 代码并且使用指针,大多数函数直接返回指向接口实现的指针。这意味着 7-Zip 代码根本不使用 QueryInterface。令人遗憾的是,.NET 的工作方式不同,对任何接口的首次访问都通过 QueryInterfaceIUnknown

所以如果我们直接使用 DLL,就会不断出现 InvalidCastException。因此,我们需要对 7-Zip 代码进行一些更改并重新编译 DLL。或者 请求 Igor Pavlov 将这些更改包含到 7-Zip 代码本身中 :)

重要更新: 从 7-Zip 4.46 alpha 版本开始,Igor 在代码中进行了必要的更改。因此,从这个版本开始,您可以直接使用格式 DLL,而无需应用任何补丁。太棒了!

**第二个**问题要小得多。它与多线程有关。如果您计划仅在一个线程中使用 7-Zip 接口,则没有问题。当您尝试在多个线程中使用一个接口时,就会出现问题。在这种情况下,除了主线程(创建接口的线程)之外的所有线程在调用任何接口方法时都会抛出异常。这是因为 RCW(Runtime Callable Wrapper)的行为。RCW 是一个在 .NET 中包装 COM 接口的对象。当您尝试在不同线程中使用接口时,RCW 会尝试封送接口但会失败(因为此实现不支持 ITypeInfo)。

幸运的是,我找到了简单的解决方案。主接口(IInArchive)返回为 IntPtr,而不是 RCW 对象。当您需要访问此接口时,调用 System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown 或任何其他相关方法来获取 RCW 对象。如果您需要在另一个线程中使用此接口,只需调用 System.Runtime.InteropServices.Marshal.FinalReleaseComObject(或 ReleaseComObject),然后围绕返回的 IntPtr 指针创建一个新的 RCW 包装器。当然,在这种情况下,您一次只能在一个线程中使用接口,但这比只能在一个线程中使用接口要好。并且任何逻辑都可以通过正确的线程锁定轻松实现。

**第三个**问题是一个广为人知的问题,但我认为应该在此提及。.NET 运行时似乎不支持 COM 接口继承(标记为 ComImport 属性的接口)。这绝对是 .NET 的一个 bug,但我不知道 Microsoft 何时会修复此 bug,或者是否会修复。

有一个简单的解决方案可以避免此 bug。继承的接口必须声明为独立的接口,并且第一个方法必须是继承接口的方法,按照它们出现的顺序排列。您可以在此翻译的源代码中看到这种“继承”的示例。

演示

应许多人的要求,我花了一些时间编写了一个小演示程序。该演示程序缺乏适当的错误检查,缺乏对不同压缩格式的支持(zip 格式在源代码中是硬编码的,但可以轻松更改),它几乎缺少所有东西,但它有两个优点:它很简单,而且它能正常工作。

演示程序只有两种模式:第一种是列出压缩文件中的所有文件,第二种是从压缩文件中提取单个文件。我认为这足以让您了解如何使用 7-Zip 接口以及如何创建更复杂的东西。

如果您想运行演示程序,请不要忘记将 7z.dll(可以在官方 7-Zip 网站上找到)放在可执行文件所在的文件夹中。

版本历史

1.5 - 添加了小型演示程序
1.3 - 为 7-Zip 4.45 中添加的功能添加了两个新的委托
1.2 - 变体类型从 IntPtr 更改为新创建的 PropVariant 结构
1.1 - 添加了流包装器,对接口翻译进行了少量更改以提高可用性
1.0 - 初始发布

© . All rights reserved.