2

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

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

显然,这在读取此数据时会带来问题,因为我的 CSVReader 使用类似fopenfread

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

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

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

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

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

谢谢

4

3 回答 3

2

CSVReader可以做以下两件事之一:

  1. 从内存解析 - 文件必须首先进行内存映射。这在不会耗尽虚拟内存的 64 位平台上是有意义的。但在 32 位平台上,这不是很灵活:您将无法打开任何超过 1 或 2 GB 大小的文件。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)};
   }
};
于 2016-11-30T19:58:03.190 回答
0

您可以利用 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()

于 2016-11-30T22:39:42.870 回答
0

我选择采用@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());
}
于 2016-11-30T21:00:22.320 回答