【问题标题】:Using qresource QFile with FILE将 qresource QFile 与 FILE 一起使用
【发布时间】:2016-11-30 18:20:26
【问题描述】:

我有一个 Qt 项目,其中有一组源/头文件,这些文件也用于其他不基于 Qt 的项目。这些文件处理 .csv 文件的读取(我将其称为我的 CSVReader 类)。 CSVReader 是在没有任何 Qt 特定函数调用的情况下编写的(我可以控制 CSVReader 的修改,但要求它不使用任何 Qt 特定代码)。

在我基于 Qt 的应用程序中,我喜欢使用 .qrc 文件将这些额外的 .csv 文件嵌入到 .exe 中,这样用户就不会意外删除或修改这些文件。

由于我的 CSVReader 使用 fopenfread 之类的调用,因此在读取此数据时显然会出现问题

我希望我可以在我的项目的 Qt 部分中使用类似以下内容 (Convert QFile to FILE*) 并将文件句柄传递给 CSVReader。

QFile myFile("goforward.raw");
myFile.open(QIODevice::ReadOnly);
int fileHandle = myFile.handle();
FILE* fh = fdopen(fileHandle, "rb");

但显然由于该文件仅存在于 .exe 中,因此对 myFile.handle() 的调用将返回 -1

我目前的想法(有点难看)是使用 QFile 打开文件,然后将文件写入硬盘,然后使用 FILE *f = fopen(fname, "rt"); 将文件加载到 CSVReader 中,然后删除我创建的文件写的。

如果其他人对如何阅读打开 qresource 文件有任何想法,我愿意接受其他想法。

谢谢

【问题讨论】:

标签: c++ qt csv qresource


【解决方案1】:

CSVReader 可以做两件事之一:

  1. 从内存中解析 - 文件必须首先被内存映射。这在不会耗尽虚拟内存的 64 位平台上是有意义的。但在 32 位平台上,这不是很灵活:您将无法打开任何超过一千兆字节或两千兆字节的文件。 CSVReader 可以处理 const char *, size_t 对。

    请注意,内存映射与显式读取文件不同。当您对文件进行内存映射时,操作系统会代表您完成读取操作:您永远不会直接从文件中读取任何内容。

    如果文件足够小以适合 32 位平台上的虚拟内存,或者如果您在 64 位平台上,这很可能是性能最佳的方法,因为现代内核的页面映射系统将提供IO 设备和解析器之间的阻抗不匹配最小。

  2. 使用抽象接口从文件中增量读取数据。 CSVReader 可以使用 InputInterface 实例。读者应该期待一个开放的接口实例。

    读者不应该打开文件本身,因为打开是特定于特定实现的。由于基于QFile 的实现将接受资源路径,而基于标准库的实现将不接受,因此使用通用open 方法毫无意义:它将隐藏否则无法通过构造实现的错误。

第二种方法似乎具有最广泛的适用性。您可以按如下方式定义接口:

// https://github.com/KubaO/stackoverflown/tree/master/questions/file-interface-40895489
#include <cstdint>

class InputInterface {
protected:
   InputInterface() {}
public:
   virtual int64_t read(char *, int64_t maxSize) = 0;
   virtual int64_t pos() const = 0;
   virtual bool seek(int64_t) = 0;
   virtual bool isOpen() const = 0;
   virtual bool atEnd() const = 0;
   virtual bool ok() const = 0;
   virtual bool flush() = 0;
   virtual void close() = 0;
   virtual ~InputInterface() {}
};

基于QFile 的实现如下所示:

#include <QtCore>

class QtFile : public InputInterface {
   QFile f;
public:
   QtFile() {}
   QtFile(const QString &name) : f(name) {}
   bool open(const QString &name, QFile::OpenMode mode) {
      close();
      f.setFileName(name);
      return f.open(mode);
   }
   bool open(QFile::OpenMode mode) {
      close();
      return f.open(mode);
   }
   void close() override {
      f.close();
   }
   bool flush() override {
      return f.flush();
   }
   int64_t read(char * buf, int64_t maxSize) override {
      return f.read(buf, maxSize);
   }
   int64_t pos() const override {
      return f.pos();
   }
   bool seek(int64_t pos) override {
      return f.seek(pos);
   }
   bool isOpen() const override {
      return f.isOpen();
   }
   bool atEnd() const override {
      return f.atEnd();
   }
   bool ok() const override {
      return f.isOpen() && f.error() == QFile::NoError;
   }
   QString statusString() const {
      return f.errorString();
   }
};

简单的 C++ 实现将是:

#include <cstdio>
#include <cerrno>
#include <cassert>
#include <string>

class CFile : public InputInterface {
   FILE *f = nullptr;
   int mutable m_status = 0;
public:
   CFile() {}
   CFile(FILE * f) : f(f) {
      assert(!ferror(f)); // it is impossible to retrieve the error at this point
      m_status = 0;
   }
   ~CFile() { close(); }
   void close() override {
      if (f) fclose(f);
      f = nullptr;
      m_status = 0;
   }
   bool open(const char *name, const char *mode) {
      close();
      f = fopen(name, mode);
      if (!f) m_status = errno;
      return f;
   }
   bool flush() override {
      auto rc = fflush(f);
      if (rc) m_status = errno;
      return !rc;
   }
   bool isOpen() const override { return f; }
   bool atEnd() const override { return f && feof(f); }
   bool ok() const override { return f && !m_status; }
   int64_t read(char * buf, int64_t maxSize) override {
      auto n = fread(buf, 1, maxSize, f);
      if (ferror(f)) m_status = errno;
      return n;
   }
   bool seek(int64_t pos) override {
      auto rc = fseek(f, pos, SEEK_SET);
      if (rc) m_status = errno;
      return !rc;
   }
   int64_t pos() const override {
      if (!f) return 0;
      auto p = ftell(f);
      if (p == EOF) {
         m_status = errno;
         return 0;
      }
      return p;
   }
   std::string statusString() const {
      return {strerror(m_status)};
   }
};

【讨论】:

  • 感谢您提供非常详细的方法。我所有的代码都将用于 64 位机器,所以内存不是问题。出于这个原因,我打算使用你的两种方法中的第一种,但是 InputInterface 的想法非常有趣,所以如果我以后需要它,我一定会记住它。
【解决方案2】:

我选择采用@Kuba 的第一种方法,因为我的应用程序没有内存限制。对于那些感兴趣的人,我在下面发布了我的纯 C++ 和基于 Qt 的方法。

纯 C++

std::ifstream ifs("file.csv");
std::string fileContents((std::istreambuf_iterator<char>(ifs)),
                         (std::istreambuf_iterator<char>()));
CSVReader csvReader(fileContents);

基于 Qt

QFile qF(":/file.csv");
if (qF.open(QFile::ReadOnly))
{
    QTextStream qTS(&qF);
    CSVReader csvReader(qTS.readAll().toStdString());
}

【讨论】:

  • 不,readAll() 必须实际为您正在读取的文件中的所有数据分配内存(因为它返回一个包含所有文件内容的QByteArray)。 @KubaOber 的方法是关于将文件数据映射到虚拟内存,这将在 Qt 中使用 QFileDevice::map() 之类的东西来完成。因此,您在应用程序中使用它,并将结果指针传递给仅限 C++ 的 CSVReader
  • 我同意迈克的观点。您不想要上面显示的任何内容。根据需要使用 QFile::map 或特定于平台的文件内存映射 API。 csvReader 可能应该将文件视为使用与 ASCII 兼容的编码进行编码,因此是 UTF-8、Latin1 等。读取 CSV 时的转码会不必要地降低性能。
  • 所以对于 Qt 实现,我将使用const char *data = qF.map(0,qF.size()); 并将dataqF.size() 作为CSVReader 的输入。非 Qt 实现会是什么样子?我希望CSVReader 的输入完全相同(即const char*size_t
【解决方案3】:

您可以利用 Qt QTemporaryFile 复制您的数据并开始。 QTemporaryfile 适用于所有操作系统。

这是一个示例(此临时文件与整个 qApp 相关联,以便在您退出应用程序后将其删除):

QTemporaryFile tmpFile(qApp);
tmpFile.setFileTemplate("XXXXXX.csv");
if (tmpFile.open()) {
    QString tmp_filename=tmpFile.fileName();
    qDebug() << "temporary" << tmp_filename;

    QFile file(":/file.csv");
    if (file.open(QIODevice::ReadOnly)) {
        tmpFile.write(file.readAll());
    }

    tmpFile.close();
}

你可以重新打开文件tmpFile.fileName()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-09
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 2015-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多