【问题标题】:CopyFileEx with progress callback in QtCopyFileEx 与 Qt 中的进度回调
【发布时间】:2013-10-02 12:30:49
【问题描述】:

谁能给我一个在 Qt 中使用 CopyFileEx 和进度回调的工作示例?

我发现了一些划痕并试图合并它但没有成功。我什至无法将 CopyProgressRoutine 函数作为 CopyFileEx 的参数传递,因为我无法声明指向该函数的指针。

我不太擅长从其他 IDE 移植代码,所以我需要你的帮助。

【问题讨论】:

  • 如果要移植代码,为什么不移植 CopyFileEx 函数并使用 QFile::copy 之类的东西?
  • 是的,我知道。但是 QFile::copy 对我来说没用,因为它不会发出 bytesWritten() 信号,所以我无法显示复制进度。对于可移植代码,我使用宏和操作系统特定的方法。通常我使用 QIODevice::read 和 ::write 但 AFIK 它比 CopyFileEx 慢所以对于 Windows 我想使用最快的。
  • 您是否只是假设 QIODevice 较慢?如果您有任何相关信息,我会很感兴趣。
  • @Merlin069 就像我说的,我必须依赖他人的经验。在我把我的问题放在这里之前,我在谷歌上搜索了好几个小时。然后我发现了很多提示和代码划痕(例如link),但我无法合并它。我读过很多次,CopyFileEx 是为 Windows 复制文件的最快和最安全的方法。我相信一些大师可以从 QIODevice 中挤出令人印象深刻的效果,但你知道我不是大师,而 CopyFileEx 正是我想要的。

标签: c++ qt


【解决方案1】:

下面的代码是一个完整的、独立的示例。它适用于 Qt 5 和 Qt 4,并使用 C++11(例如 Visual Studio 2015 及更高版本)。

main.cpp

// https://github.com/KubaO/stackoverflown/tree/master/questions/copyfileex-19136936
#include <QtGui>
#include <QtConcurrent>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
#include <windows.h>
#include <comdef.h>
//#define _WIN32_WINNT _WIN32_WINNT_WIN7

static QString toString(HRESULT hr) {
   _com_error err{hr};
   return QStringLiteral("Error 0x%1: %2").arg((quint32)hr, 8, 16, QLatin1Char('0'))
         .arg(err.ErrorMessage());
}

static QString getLastErrorMsg() {
   return toString(HRESULT_FROM_WIN32(GetLastError()));
}

static QString progressMessage(ULONGLONG part, ULONGLONG whole) {
   return QStringLiteral("Transferred %1 of %2 bytes.")
         .arg(part).arg(whole);
}

class Copier : public QObject {
   Q_OBJECT

   BOOL m_stop;
   QMutex m_pauseMutex;
   QAtomicInt m_pause;
   QWaitCondition m_pauseWait;

   QString m_src, m_dst;
   ULONGLONG m_lastPart, m_lastWhole;
   void newStatus(ULONGLONG part, ULONGLONG whole) {
      if (part != m_lastPart || whole != m_lastWhole) {
         m_lastPart = part;
         m_lastWhole = whole;
         emit newStatus(progressMessage(part, whole));
      }
   }
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
   static COPYFILE2_MESSAGE_ACTION CALLBACK copyProgress2(
         const COPYFILE2_MESSAGE *message, PVOID context);
#else
   static DWORD CALLBACK copyProgress(
         LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred,
         LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred,
         DWORD streamNo, DWORD callbackReason, HANDLE src, HANDLE dst,
         LPVOID data);
#endif
public:
   Copier(const QString & src, const QString & dst, QObject * parent = nullptr) :
      QObject{parent}, m_src{src}, m_dst{dst} {}
   Q_SIGNAL void newStatus(const QString &);
   Q_SIGNAL void finished();
   /// This method is thread-safe
   Q_SLOT void copy();
   /// This method is thread-safe
   Q_SLOT void stop() {
      resume();
      m_stop = TRUE;
   }
   /// This method is thread-safe
   Q_SLOT void pause() {
      m_pause = true;
   }
   /// This method is thread-safe
   Q_SLOT void resume() {
      if (m_pause)
         m_pauseWait.notify_one();
      m_pause = false;
   }
   ~Copier() override { stop(); }
};

#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
void Copier::copy() {
   m_lastPart = m_lastWhole = {};
   m_stop = FALSE;
   m_pause = false;
   QtConcurrent::run([this]{
      COPYFILE2_EXTENDED_PARAMETERS params{
         sizeof(COPYFILE2_EXTENDED_PARAMETERS), 0, &m_stop,
               Copier::copyProgress2, this
      };
      auto rc = CopyFile2((PCWSTR)m_src.utf16(), (PCWSTR)m_dst.utf16(), &params);
      if (!SUCCEEDED(rc))
         emit newStatus(toString(rc));
      emit finished();
   });
}
COPYFILE2_MESSAGE_ACTION CALLBACK Copier::copyProgress2(
      const COPYFILE2_MESSAGE *message, PVOID context)
{
   COPYFILE2_MESSAGE_ACTION action = COPYFILE2_PROGRESS_CONTINUE;
   auto self = static_cast<Copier*>(context);
   if (message->Type == COPYFILE2_CALLBACK_CHUNK_FINISHED) {
      auto &info = message->Info.ChunkFinished;
      self->newStatus(info.uliTotalBytesTransferred.QuadPart, info.uliTotalFileSize.QuadPart);
   }
   else if (message->Type == COPYFILE2_CALLBACK_ERROR) {
      auto &info = message->Info.Error;
      self->newStatus(info.uliTotalBytesTransferred.QuadPart, info.uliTotalFileSize.QuadPart);
      emit self->newStatus(toString(info.hrFailure));
      action = COPYFILE2_PROGRESS_CANCEL;
   }
   if (self->m_pause) {
      QMutexLocker lock{&self->m_pauseMutex};
      self->m_pauseWait.wait(&self->m_pauseMutex);
   }
   return action;
}
#else
void Copier::copy() {
   m_lastPart = m_lastWhole = {};
   m_stop = FALSE;
   m_pause = false;
   QtConcurrent::run([this]{
      auto rc = CopyFileExW((LPCWSTR)m_src.utf16(), (LPCWSTR)m_dst.utf16(),
                            &copyProgress, this, &m_stop, 0);
      if (!rc)
         emit newStatus(getLastErrorMsg());
      emit finished();
   });
}
DWORD CALLBACK Copier::copyProgress(
      const LARGE_INTEGER totalSize, const LARGE_INTEGER totalTransferred,
      LARGE_INTEGER, LARGE_INTEGER, DWORD,
      DWORD, HANDLE, HANDLE,
      LPVOID data)
{
   auto self = static_cast<Copier*>(data);
   self->newStatus(totalTransferred.QuadPart, totalSize.QuadPart);
   if (self->m_pause) {
      QMutexLocker lock{&self->m_pauseMutex};
      self->m_pauseWait.wait(&self->m_pauseMutex);
   }
   return PROGRESS_CONTINUE;
}
#endif

struct PathWidget : public QWidget {
   QHBoxLayout layout{this};
   QLineEdit edit;
   QPushButton select{"..."};
   QFileDialog dialog;
   explicit PathWidget(const QString & caption) : dialog{this, caption} {
      layout.setMargin(0);
      layout.addWidget(&edit);
      layout.addWidget(&select);
      connect(&select, SIGNAL(clicked()), &dialog, SLOT(show()));
      connect(&dialog, SIGNAL(fileSelected(QString)), &edit, SLOT(setText(QString)));
   }
};

class Ui : public QWidget {
   Q_OBJECT
   QFormLayout m_layout{this};
   QPlainTextEdit m_status;
   PathWidget m_src{"Source File"}, m_dst{"Destination File"};
   QPushButton m_copy{"Copy"};
   QPushButton m_cancel{"Cancel"};

   QStateMachine m_machine{this};
   QState s_stopped{&m_machine};
   QState s_copying{&m_machine};

   Q_SIGNAL void stopCopy();
   Q_SLOT void startCopy() {
      auto copier = new Copier(m_src.edit.text(), m_dst.edit.text(), this);
      connect(copier, SIGNAL(newStatus(QString)), &m_status, SLOT(appendPlainText(QString)));
      connect(copier, SIGNAL(finished()), SIGNAL(copyFinished()));
      connect(copier, SIGNAL(finished()), copier, SLOT(deleteLater()));
      connect(this, SIGNAL(stopCopy()), copier, SLOT(stop()));
      copier->copy();
   }
   Q_SIGNAL void copyFinished();
public:
   Ui() {
      m_layout.addRow("From:", &m_src);
      m_layout.addRow("To:", &m_dst);
      m_layout.addRow(&m_status);
      m_layout.addRow(&m_copy);
      m_layout.addRow(&m_cancel);

      m_src.dialog.setFileMode(QFileDialog::ExistingFile);
      m_dst.dialog.setAcceptMode(QFileDialog::AcceptSave);
      m_status.setReadOnly(true);
      m_status.setMaximumBlockCount(5);

      m_machine.setInitialState(&s_stopped);
      s_stopped.addTransition(&m_copy, SIGNAL(clicked()), &s_copying);
      s_stopped.assignProperty(&m_copy, "enabled", true);
      s_stopped.assignProperty(&m_cancel, "enabled", false);
      s_copying.addTransition(&m_cancel, SIGNAL(clicked()), &s_stopped);
      s_copying.addTransition(this, SIGNAL(copyFinished()), &s_stopped);
      connect(&s_copying, SIGNAL(entered()), SLOT(startCopy()));
      connect(&s_copying, SIGNAL(exited()), SIGNAL(stopCopy()));
      s_copying.assignProperty(&m_copy, "enabled", false);
      s_copying.assignProperty(&m_cancel, "enabled", true);
      m_machine.start();
   }
};

int main(int argc, char *argv[])
{
   QApplication a{argc, argv};
   Ui ui;
   ui.show();
   return a.exec();
}
#include "main.moc"

【讨论】:

  • 这就是我要找的!我在 Qt 5 下对其进行了测试,它就像一个魅力。非常感谢!
  • 如果从通过 worker 移动到工作线程的工作对象运行,如何将 CopyFileEx 示例修改为线程安全的->moveToThread(workerThread);称呼?最好在与其他工作线程对象逻辑相同的线程中执行 CopyFileEx,而不是通过 QtConcurrent 在另一个线程中旋转...但这只是我的风格:)
  • 不幸的是,CopyFileEx 为自己占用了整个线程,因此在复制过程中没有其他逻辑可以在该线程中工作 - 它垄断了它。 CopyFileEx 不进行消息分发,也不旋转事件循环。没有什么可以让它“更”线程安全:即使你不使用QtConcurrent,标记为线程安全的方法仍然是线程安全的——是的,你当然可能想要移动@987654325 @ 指向使用它的线程,即调用所有其他非线程安全方法的线程。在这方面,CopyFile[Ex|2] 是一个不幸的 PITA。
  • 除了明显不同的类成员和实现之外,CopyFileEx 和 CopyFile2 有什么区别?那些可怕的互斥锁也只是为了暂停对吧?
  • 如果我错了,请纠正我 - 我使用 CopyCallEx 进行了实验。我做了一个 qDebug()
猜你喜欢
  • 2018-08-24
  • 2020-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多