【问题标题】: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 使用 fopen 和 fread 之类的调用,因此在读取此数据时显然会出现问题

我希望我可以在我的项目的 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()); 并将data 和qF.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
    相关资源
    最近更新 更多