【问题标题】:C - Storing a large group of files as a single resourceC - 将一大组文件存储为单个资源
【发布时间】:2015-11-16 00:01:46
【问题描述】:

如果这个问题有明显的答案,请原谅我;我还没有找到它,因为我不完全确定我在寻找什么。这很可能重复了一个我没有找到的问题;对不起。

我有一个使用文本、音频、视频、图标和各种不同文件类型的 C 可执行文件。这些文件存储在本地;文件夹结构又大又深,需要与应用程序一起安装才能正常运行(并不是说我预计它会分发,我希望打包自己的工作以方便起见)。

在我个人看来,如果文件库存储在一个可供应用程序访问的单个文件中,例如在/usr/bin/APPLICATION 旁边或在最合适的位置,会更方便;需要时由可执行文件访问。

我搜索了类似的问题并找到了表明两个可能选项的建议 Resource Files 似乎是 Windows 的原生选项和 Including files at compile。第一个问题导致与第二个问题相似的答案,并且没有回答与 linux 可执行文件的资源文件存在相关的问题。它(与第二个一样)着眼于在编译过程中包含数据文件。这不像我只想更新我的资源那样有用,我不得不重新编译整个应用程序(媒体是动态添加的)。

问题:有没有办法将各种文件类型存储在一个可供 linux 中的可执行文件访问的文件中,如果可以,您将如何实现?

我最初对此的想法是创建一个.zip.gz 文件,这也可能提供压缩作为额外的好处,但我不知道如何(或者甚至可能)访问此类文件中的数据在飞行中。我同样不确定是否有特定的文件类型或库可以提供更合适的解决方案。此外,我对.dat 文件几乎一无所知,这些文件可以在 linux 系统上的这种情况下使用吗?

【问题讨论】:

  • .dat 文件可以代表任何东西;我不知道标准的.dat 文件格式。 .zip 听起来是个不错的选择(java 将它用于罐子);查看libzip
  • 显然 Zip 可以轻松地仅提取您想要的文件,但不能提取 tar/gz。见gamedev.stackexchange.com/questions/37648/…
  • 您可以将所有文件名存储在一个由程序解析的文本文件中(每种文件类型都有部分,或者每种文件类型可能有一个文本文件)。然后,您不需要继续重新编译源代码,您可能会在其中对资源文件名进行硬编码。 “资源文件”是一个通常与编译相关的术语,例如定义图标、光标和对话框,而不是要处理的数据。
  • 许多程序使用“zip”格式,使用不同的文件扩展名来假装它不是 zip。 Office 2007 和更新版本使用 .docx、.xlsx 等,它们实际上都是 .zip 文件。 Java 使用的 .jar 实际上也是一个 .zip 文件,甚至像 Quake 3(顺便说一句,它是开源的)这样的一些游戏也使用 .pak ,它是另一个伪装的 .zip 文件。我只能假设那里有一个库,它使得使用 zip 存储静态内容非常容易使用。
  • 在 Mac OS X 上,.app 程序实际上是以指定方式布局的目录和文件结构,并被标记为像程序一样对待...甚至没有 .zip 压缩成单个文件。

标签: c linux file


【解决方案1】:

我完全不明白您为什么要使用单个文件。考虑到文件提取增加的复杂性(以及增加错误潜入的可能性)和相关的开销,我看不出它会如何“更方便”。

我有一个使用文本、音频、视频、图标和各种不同文件类型的 C 可执行文件。

许多其他 Linux 应用程序也是如此。使用包管理时,通常的方法是将应用程序/usr/bin/YOURAPP 的架构独立数据(图标、音频、视频等)放入/usr/share/YOURAPP/,并将架构相关数据(如帮助程序二进制文件)放入/usr/lib/YOURAPP .后两者是完整的目录树非常常见,有时非常深和宽。

对于本地编译的东西,通常将它们放在/usr/local/bin/YOURAPP/usr/local/share/YOURAPP//usr/local/share/YOURAPP/ 中,以避免混淆包管理器。 (如果您检查./configure 脚本或阅读Makefiles,这是他们支持的PREFIX 变量的主要目的。)

/usr/bin/YOURAPP 也很常见,它是一个简单的 shell 脚本,设置环境变量,或检查用户特定的覆盖(来自 $HOME/.YOURAPP/),以 exec /usr/lib/YOURAPP/YOURAPP.bin [parameters...] 结尾,它将 shell 替换为实际的二进制可执行文件,无需将 shell 留在内存中。

例如,我机器上的/usr/share/octave/ 总共包含 138 个目录(在最多 7 个目录深的层次结构中)和 1463 个文件;总共有大约十兆字节的“东西”。 LibreOffice、Eagle、Fritzing 和 KiCAD 每个都需要数百兆字节,因此 Octave 也不是一个极端的例子。

【讨论】:

  • 我同意,请参阅Filesystem Hierarchy Standard 了解有关放置内容的详细信息。
  • 我认为这从我是否应该为应用程序创建单个资源文件的角度回答了这个问题。拥有大量资源,我实际上并没有那么难过。
  • 我确实认为在某些情况下,个人可能不希望他们的资源可用,例如许可的软件或游戏。资源的生成可能很耗时,并且个人可能不希望将它们单独分发。不过,我同意你在这方面的看法,所以谢谢。
  • @Chortle:加密资源文件或混淆档案文件中的内容通常不是您认为的威慑力。如果有人想要内容,他们得到它。看看游戏,或者任何专有软件,真的。通常,通过对资源使用自定义文件格式来实现更明智的平衡。足以阻止“意外”复制,但不会太复杂而不会减慢 I/O 或需要大量开销(内存、使用的 CPU 时间)来实现。
  • 有效点我想这是关于在这种情况下采取合理的措施来保护您的版权资源;自定义文件格式似乎是一个不错的选择,尽管与任何公开的文件一样,您必须耸耸肩并接受它可能还不够。我对如何将自定义文件或任何文件放在一起非常感兴趣。特别是在阅读了 KemyLand 的回复之后。这有点超出我的想象,但很有趣。
【解决方案2】:

您有多种选择(TODO:添加更多;)):

您可以阅读一些归档文件格式规范,编写代码来读取/写入这些归档文件,但这样做会浪费您的时间。

您可以发明一种肮脏、简单的文件格式,例如(“dsa”代表“Dirty and Simple Archiver”):

#include <stdint.h>

// Located at the beginning of the file    
struct DSAHeader {
    char            magic[3];            // Shall be (char[]) { 'D', 'S', 'A' }
    unsigned char   endianness;          // The rest of the file is translated according to this field. 0 means little-endian, 1 means big-endian.
    unsigned char   checksum[16];         // MD5 sum of the whole file. (when calculating checksums, this field is psuedo-filled with zeros).
    uint32_t        fileCount;
    uint32_t        stringTableOffset;   // A table containing the files' names.
};

// A dsaHeader.fileCount-sized array of DSAInodeHeader follows the DSAHeader.
struct DSANodeHeader {
    unsigned char   type;              // 0 means directory, 1 means regular file.
    uint32_t        parentOffset;      // Pointer to the parent directory, or zero if the node is in the root.
    uint32_t        offset;            // The node's type-dependent header starts here.
    uint32_t        nodeSize;          // In bytes for files, and in number of entries for directories.
    uint32_t        dataOffset;        // The file's data starts at this offset for files, and a pointer to the first DSADirectoryEntryHeader for directories.
    uint32_t        filenameOffset;    // Relative to the string table.
};

typedef uint32_t    DSADirectoryEntryHeader;    // Offset to the entry's DSANodeHeader

“字符串表”是一个以空字符结尾的字符串的连续序列。

这种格式非常简单(并且可移植;))。而且,作为奖励,如果您想要(解)压缩,您可以使用 Zip、BZ2 或 XZ 之类的东西来(解)压缩您的文件(这些程序/格式与归档程序无关,即不依赖于 tar,如普遍认为)。

作为最后(或第一个?)的手段,您可以使用现有的库/API 来操作存档器和压缩文件格式。

编辑:添加了对目录的支持:)。

【讨论】:

  • 我真的很喜欢文件格式的想法,尽管这可能有点超出我的技能范围,您是否需要使用函数将数据序列化为unit32_t?然后去序列化还是我完全离开这里?
  • 你是对的。如果您愿意,您可以去掉 endianness 字段并进行简单的原始读取和写入。但是,如果你想真正便携,你应该把那个字段留在那里,并根据需要在小端和大端之间进行转换。出于肮脏和快速的目的,如果您愿意,您也可以暂时删除校验和。
  • 这听起来很愚蠢,但什么是字节序?我很想阅读自定义文件格式,你知道有什么好的资源吗?我可以用谷歌搜索,但质量如此复杂,总是值得先问一下。
  • 好吧,我可能不是向您解释什么是字节序的最佳人选。谷歌它或其他东西:)。现在,你现在应该知道,如果不同的机器有不同的字节序,结构字段中的数据就会被弄乱。为了让您冷静一点,字节序在所有 PC(英特尔架构)中都是相同的,即所谓的“小端序”。
  • 关于文件格式,您可以通过阅读this very list of various file formats, with links to documentation et al 锁定一些宇宙生命周期。但是,“自定义”文件格式本质上是自定义的,因此它们没有标准化/没有规范:)。但是,例如,如果我要为上面的“DSA”格式编写文档,它会立即标准化,但我没有,所以不是。
【解决方案3】:

假设你有:

top-level-folder/
  |
   - your-linux-executable
   - icon-files-folder/
   - image-files-folder/
   - other-folders/
   - other-files

这样做(在top-level-folder 内)

tar zcvf my-package.tgz top-level-folder

要展开,请执行以下操作:

tar zxvf my-package.tgz

【讨论】:

    【解决方案4】:

    我有一个使用文本、音频、视频、图标和各种不同文件类型的 C 可执行文件。这些文件存储在本地;文件夹结构又大又深,需要与应用程序一起安装才能正常运行。

    考虑到相关不同文件类型的复杂性以及大而深的文件夹结构以及需要与应用程序一起安装。如果您想动态更改资源,添加单个资源文件将很困难,或者说几乎不可能跟踪更改。当然,向可执行文件添加资源不是一种选择,因为它会增加可执行文件的大小,并且在更新资源时需要经常重新编译。

    在考虑了项目的所有方面后,在我看来,解决方案应该是使用 INI 文件。 INI 将存储在确定的位置,其他资源位置应在 INI 文件中提供。与 INI 一样,您可以轻松存储资源的位置、哈希键和大小,并且可以轻松检查更改或更新资源。

    由于您使用的是文件类型的已压缩版本,因此通用压缩算法将无法工作,因为速率会非常低。因此建议使用 7z 算法进行压缩。从各种算法中,我建议选择 xz 压缩算法,因为许多开源项目目前使用它来压缩二进制文件并减小大小。

    Foreach 文件压缩其 crc32 或哈希值也应包含在 INI 文件中,以检查传输数据的有效性。

    【讨论】:

      猜你喜欢
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-22
      相关资源
      最近更新 更多