我正在使用ReadFileEx从文件中读取一些字节,并使用WriteFileEx将一些字节写入设备。此操作将重复,直到所有文件字节都被读取并写入设备。
我使用 Ex API 的原因是,通过向操作系统请求重叠的 IO,可以在读/写函数执行任务时保持 UI 线程响应更新进度条。
该过程从 ReadFileEx 和MY_OVERLAPPED
结构开始,ReadCompletionRoutine
将一起传入。读取完成后,将调用读取完成例程。在例程中,将发出 WriteFileEx,并调用 WriteCompletionRoutine。在写入完成例程中,在将MY_OVERLAPPED结构的偏移量重置到下一个位置后,将发出另一个 ReadFileEx。也就是说,一旦完成读取或写入,两个完成将相互调用。
请注意,仅当调用线程处于可警报状态时,才会执行上述进程。我使用 while 循环通过不断检查全局状态变量是否设置为 TRUE 来保持线程可警报。完成所有过程后,状态变量completed
将在ReadCompletionRoutine
内部设置为 TRUE。
仅供参考,MY_OVERLAPPED
结构是一个继承OVERLAPPPED
结构的自定义结构,以便我可以向其添加所需的 2 个信息。
现在,我的问题是我想添加一个取消功能,以便用户可以取消所有已启动的进程。我所做的很简单。当按下取消按钮时,我将completed
变量设置为 TRUE,因此 while 循环将中断并停止可警报状态,因此不会执行完成例程。但是,我不知道如何取消Read/WriteFileEx
发送的重叠请求及其完成例程以及MY_OVERLAPPED
结构(请参阅代码中的//******* 部分(。现在,一旦按下取消按钮,我的代码就会崩溃。取消部分是导致崩溃的部分。请帮忙,谢谢。
//In MyClass.h========================================
struct MY_OVERLAPPED: OVERLAPPED {
MyClass *event;
unsigned long long count;
};
//In MyClass.cpp - main===============================
MY_OVERLAPPED overlap;
memset(&overlap, 0,sizeof(overlap));
//point to this class (MyClass), so all variables can later be accessed
overlap.event = this;
//set read position
overlap.Offset = 0;
overlap.OffsetHigh = 0;
overlap.count = 0;
//start the first read io request, read 524288 bytes, which 524288 bytes will be written in ReadCompletionRoutine
ReadFileEx(overlap.event->hSource, overlap.event->data, 524288, &overlap, ReadCompletionRoutine);
while(completed != true) {
updateProgress(overlap.count);
SleepEx(0,TRUE);
}
//********
CancelIo(overlap.event.hSource);
CancelIo(overlap.event.hDevice);
//********
//In MyClass.cpp - CALLBACKs===============================
void CALLBACK ReadCompletionRoutine(DWORD errorCode, DWORD bytestransfered, LPOVERLAPPED lpOverlapped)
{
//type cast to MY_OVERLAPPED
MY_OVERLAPPED *overlap = static_cast<MY_OVERLAPPED*>(lpOverlapped);
//write 524288 bytes and continue to read next 524288 bytes in WriteCompletionRoutine
WriteFileEx(overlap->event->hDevice, overlap->event->data, 524288, overlap, WriteCompletionRoutine);
}
void CALLBACK WriteCompletionRoutine(DWORD errorCode, DWORD bytestransfered, LPOVERLAPPED lpOverlapped)
{
MY_OVERLAPPED *overlap = static_cast<MY_OVERLAPPED*>(lpOverlapped);
if(overlap->count<fileSize/524288) {
//set new offset to 524288*i, i = overlap->count for next block reading
overlap->count = (overlap->count)+1;
LARGE_INTEGER location;
location.QuadPart = 524288*(overlap->count);
overlap->Offset = location.LowPart;
overlap->OffsetHigh = location.HighPart;
ReadFileEx(overlap->event->hSource, overlap->event->data, 524288, overlap, ReadCompletionRoutine);
}
else {
completed = TRUE;
}
}
请注意,我不喜欢使用多线程编程。除此之外,任何实现相同目标的更好方法都是值得赞赏的。请随时提供详细的代码和解释。谢谢。
我实际上会为此使用后台线程,因为现代C++使这变得非常容易。 当然,这比你现在要做的要容易得多。 因此,请尝试摆脱您可能拥有的任何先入之见,即这对您来说是错误的方法,请尝试本着其意图的精神阅读这篇文章。 谢谢。
首先,这里有一些非常简单的概念验证代码,您可以自己编译并运行以试用。 乍一看,这可能看起来有点"那又怎样?",但请耐心等待,我将在最后解释:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <thread>
#include <chrono>
#include <memory>
#include <atomic>
int usage ()
{
std::cout << "Usage: copy_file infile outfilen";
return 255;
}
void copy_file (FILE *infile, FILE *outfile, std::atomic_bool *cancel)
{
constexpr int bufsize = 32768;
std::unique_ptr <char []> buf (new char [bufsize]);
std::cout << "Copying: ";
while (1)
{
if (*cancel)
{
std::cout << "nCopy cancelled";
break;
}
size_t bytes_read = fread (buf.get (), 1, bufsize, infile);
if (bytes_read == 0)
{
// Check for error here, then break out of the loop
break;
}
size_t bytes_written = fwrite (buf.get (), 1, bytes_read, outfile);
// Again, check for error etc
std::cout << ".";
}
std::cout << "nCopy completen";
// Now probably something like PostMessage here to alert your main loop hat the copy is complete
}
int main (int argc, char **argv)
{
if (argc < 3) return usage ();
FILE *infile = fopen (argv [1], "rb");
if (infile == NULL)
{
std::cout << "Cannot open input file " << argv [1] << "n";
return 255;
}
FILE *outfile = fopen (argv [2], "wb");
if (outfile == NULL)
{
std::cout << "Cannot open output file " << argv [2] << "n";
fclose (infile);
return 255;
}
std::atomic_bool cancel = false;
std::thread copy_thread = std::thread (copy_file, infile, outfile, &cancel);
std::this_thread::sleep_for (std::chrono::milliseconds (200));
cancel = true;
copy_thread.join (); // waits for thread to complete
fclose (infile);
fclose (outfile); // + error check!
std::cout << "Program exitn";
}
当我在我的机器上运行它时,我得到类似的东西:
background_copy_test bigfile outfile
Copying:
.....................................................................................
..............
Copy cancelled
Copy complete
Program exit
那么,这有什么值得注意的呢?好吧,没有特别的顺序:
- 这很简单。
- 这是标准C++。 那里根本没有Windows调用(我故意这样做,试图说明一个观点(。
- 这是万无一失的。
- 它"只是工作"。
当然,现在,当您在现实生活中复制文件时,您不会让主线程进入睡眠状态。 不 不 不。 相反,您只需通过std::thread
启动副本,然后放置"复制..."。带有"取消"按钮的对话框(大概,这将是一个模式对话框(
然后:
- 如果按下该按钮,只需将
cancel
设置为true
,魔术就会发生。 - 完成后,
copy_file
向"复制"对话框发送WM_APP+nnn
消息。 它也可以这样做,让对话框更新其进度条(我把所有这些东西都留给你(。 - 在你破坏
copy_thread
之前,不要忽略对join()
的呼唤,否则它就会超出范围!
还有什么? 好吧,为了正确理解这个问题,请学习一些现代C++。 CPP偏好是一个有用的网站,但你真的应该读一本好书。 然后,您应该能够将此处学到的经验教训应用于您的特定用例。
编辑:我想说的是,您最好在"复制"对话框的WM_INITDIALOG
处理程序中创建线程。 然后,您可以将对话框的HWND
传递给copy_file
,以便它知道将这些消息发送到何处。 只是一个想法。
如果你想从这篇文章中获利,你有很多阅读工作要做。 但话又说回来,你应该这样做。 我担心这篇文章将一事无成。 羞耻。