相信大家或多或少都遇到过想删除一个文件,却提示被占用的情况:

不知道各位都是如何处理的,反正我一直都是用的火绒。但是作为一名程序员,自己写一个小程序实现多有意思,是吧。况且为了一个小工具去安装一个杀毒软件,不是一个合格的程序员,你们说对不对。基于以上的原因,最终出现了这篇文章,效果如下,本文所对应的完整代码已上传到GitHub,可自行取用~~~

在正式编码之前,这里先介绍一些已有的工具,如果想看编码实现,可以跳过本节。
这里以火绒自带的工具为例,使用方式如下所示:


通过火绒自带的工具,可以看到文件被什么程序占用了,然后进行解锁。
Unlocker、LockHunter、IObit Unlocker,由于未实际使用过,这里不再展开介绍。


通过Windows 自带的任务管理器也可以查询文件的占用状态,缺点是无法只解锁文件,只能关闭占用的进程。
Sysinternals 是 Windows 平台上使用的一个工具集合,可以监控系统的绝大部分文件,磁盘,网络,进程线程,模块,工具全集可以在微软官网进行下载,这里只讲解用于句柄操作的 Handle:
首先在官网进行下载,可以发现包含的文件很简单,exe 文件可以直接运行:

在这里我们选择其中的 handle64 即可,首先以管理员身份运行终端,然后运行以下命令:

然后我们就可以看到上图所示的占用的程序进程号和对应的文件句柄,之后我们就可以运行以下命令去解除占用了,其中 1CE8 和 20392 分别是上述命令获取到的文件句柄和占用进程号:

以上讲解了一些解除文件占用的第三方功能,下面则开始步入正题,从零实现一个解除文件占用的小工具。
Windows11
Visual Studio 2022
Qt5.15.2/QML(用于展示简单结果文本,不了解 Qt 也没什么影响)
Inno Setup(用于创建程序的安装程序)
首先说明以下程序的整体思路:程序初始判断是否有传参,如果无参说明程序是手动运行,执行添加注册表实现右键菜单包含解锁文件选项的逻辑。如果包含参数,说明程序是通过右键菜单运行的,根据传递的参数(即文件路径)执行相应的文件解锁操作。
以下不展示全部代码,完整代码可在前言中的GitHub查看,全部逻辑都在 main.cpp 中。
最终效果如下:


结合上图和以下代码即注释,相关代码不难理解,主要步骤如下:
使用注册表时要特别注意文件编码,字符串类型转换的处理。
实现的效果如下,其中解锁文件就是我们创建的:

这部分逻辑稍微复杂一些,具体步骤如下:
特别注意,在 ring3 级调用NtQueryObject会出现阻塞的情况,因此需要通过开一个线程增加超时处理,避免程序卡住。此外,由于是跨进程处理句柄,因此需要调用DuplicateHandle方法。
界面展示这里使用了 Qt 的 QML 进行实现,页面比较简单,包含以下两个界面。
主界面只是简单展示一下文本,其中文本会根据注册表添加成功或失败展示相应的信息(在注册表功能实现部分的代码开头可以看到)。
解锁界面稍微复杂一些,通过 Timer 定时器实现动态的查找中...展示,在解锁文件完成后会通过showFile函数展示占用的进程名。
其中设置进程名的代码操作在 main.cpp 文件中:
最后再介绍如何制作程序的安装程序,前提是需要先对 Qt 程序进行打包(此处省略 500 字),然后就可以使用Inno Setup工具进行制作了,步骤如下:
设置应用的名称版本:

设置应用的安装路径,同时允许用户进行自定义:

设置执行程序的路径和根文件夹路径:

之后全部点击下一步,然后在选择语言时按需选择:

然后可以设置程序的图标和安装程序输出路径,之后全部点击下一步即可:

然后就可以在输出路径看到生成的安装程序:

点击运行就是熟悉的程序安装界面了,按需进行选择后即可使用,同时需要以管理员身份运行:

安装程序也可以在GitHub中找到,目前只在 win10 和 win11 进行了测试。
本文讲解了如何实现一个解除文件占用的小程序,不过还存在很多不完善的地方:
不过相信各位参考本文的思路,实现以上的功能也是轻而易举,欢迎一起交流讨论~~~
所以,我还是选择使用火绒。
在实现这个小工具的过程中,踩了很多坑,非常感谢以下文章所提供的解决思路:
QVariant showInfo;
string appPath = QCoreApplication::applicationDirPath()
.replace(QRegExp("/"), "\\").toStdString() + "\\unlockfile.exe";
if (setRightMenu("unlockfile", "解锁文件", appPath))
{
showInfo = u8"注册表添加成功";
}
else
{
showInfo = u8"注册表添加失败, 请确保以管理员身份运行";
}
QMetaObject::invokeMethod(root, "showInfo", Q_ARG(QVariant, showInfo));
bool setRightMenu(string strRegKeyKey, string strRegKeyName, string strApplication)
{
HKEY hresult;
string strRegKey = "*\\shell\\" + strRegKeyKey;
string strRegSubkey = strRegKey + "\\command";
string strApplicationValue = "\"" + strApplication + "\"" + " \"%1\"";
DWORD dwPos;
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegKey.c_str()), 0,
NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strRegKeyName.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, stringToWString("Icon"), 0, REG_SZ, (BYTE*)stringToWString(strApplication.c_str()), (wcslen(stringToWString(strApplication.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegSubkey.c_str()), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strApplicationValue.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
RegCloseKey(hresult);
return true;
}
QVariant showInfo;
string appPath = QCoreApplication::applicationDirPath()
.replace(QRegExp("/"), "\\").toStdString() + "\\unlockfile.exe";
if (setRightMenu("unlockfile", "解锁文件", appPath))
{
showInfo = u8"注册表添加成功";
}
else
{
showInfo = u8"注册表添加失败, 请确保以管理员身份运行";
}
QMetaObject::invokeMethod(root, "showInfo", Q_ARG(QVariant, showInfo));
bool setRightMenu(string strRegKeyKey, string strRegKeyName, string strApplication)
{
HKEY hresult;
string strRegKey = "*\\shell\\" + strRegKeyKey;
string strRegSubkey = strRegKey + "\\command";
string strApplicationValue = "\"" + strApplication + "\"" + " \"%1\"";
DWORD dwPos;
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegKey.c_str()), 0,
NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strRegKeyName.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, stringToWString("Icon"), 0, REG_SZ, (BYTE*)stringToWString(strApplication.c_str()), (wcslen(stringToWString(strApplication.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegCreateKeyEx(HKEY_CLASSES_ROOT, stringToWString(strRegSubkey.c_str()), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS, NULL, &hresult, &dwPos) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
if (RegSetValueEx(hresult, NULL, 0, REG_SZ, (BYTE*)stringToWString(strApplicationValue.c_str()), (wcslen(stringToWString(strApplicationValue.c_str())) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
RegCloseKey(hresult);
return false;
}
RegCloseKey(hresult);
return true;
}
DWORD queryObj(LPVOID lpParam)
{
return NtQueryObject(hCopy, 1, pObject, MAX_PATH * 2, NULL);
}
void getFileName(string& fileName)
{
pObject = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * 2);
if (pObject == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
HANDLE hThread = CreateThread(NULL, 0, queryObj, NULL, 0, NULL);
if (hThread == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
DWORD dwSatus = WaitForSingleObject(hThread, 200);
if (dwSatus == WAIT_TIMEOUT)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
if (pObject->NameBuffer != NULL)
{
DWORD n = WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, NULL, 0, NULL, FALSE);
char* name = new char[n + 1];
memset(name, 0, n + 1);
WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, name, n, NULL, FALSE);
fileName = name;
delete[] name;
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
bool init()
{
HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
if (hNtDll == NULL)
{
return false;
}
NTQUERYSYSTEMINFOMATION NtQuerySystemInformation = (NTQUERYSYSTEMINFOMATION)GetProcAddress(hNtDll, "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL)
{
return false;
}
nulFileHandle = CreateFile(L"NUL", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
if (nulFileHandle == NULL)
{
return false;
}
NtQueryObject = (PNtQueryObject)GetProcAddress(hNtDll, "NtQueryObject");
DWORD nSize = 4096;
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
while (NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, nSize, NULL) == STATUS_INFO_LENGTH_MISMATCH)
{
HeapFree(GetProcessHeap(), 0, pHandleInfo);
nSize += 4096;
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), 0, nSize);
}
if (pHandleInfo == NULL)
{
return false;
}
return true;
}
int getFileObjectTypeNumber()
{
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);
if ((int)GetCurrentProcessId() == pHandle->ProcessId && pHandle->Handle == (USHORT)nulFileHandle)
{
return (int)pHandle->ObjectTypeNumber;
}
}
return 40;
}
void closeFile(string& closeFileName)
{
int fileObjectTypeNumber = getFileObjectTypeNumber();
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
PSYSTEM_HANDLE pHandle = (PSYSTEM_HANDLE) & (pHandleInfo->HandleInfo[i]);
if (pHandle->ObjectTypeNumber != fileObjectTypeNumber || pHandle->ProcessId == 4 || pHandle->Handle == 0)
{
continue;
}
HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pHandle->ProcessId);
if (hProcess == NULL)
{
continue;
}
hCopy = 0;
if (!DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &hCopy, MAXIMUM_ALLOWED, FALSE, 0))
{
continue;
}
int pid = pHandle->ProcessId;
string fileName;
getFileName(fileName);
if (fileName.find(closeFileName) != -1)
{
WCHAR tmpName[MAX_PATH] = {};
DWORD size = MAX_PATH;
QueryFullProcessImageName(hProcess, 0, tmpName, &size);
wStringToString(processName, tmpName);
HANDLE h_tar = NULL;
if (DuplicateHandle(hProcess, (HANDLE)pHandle->Handle, GetCurrentProcess(), &h_tar, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
{
CloseHandle(h_tar);
}
CloseHandle(hCopy);
CloseHandle(hProcess);
return;
}
CloseHandle(hCopy);
CloseHandle(hProcess);
}
HeapFree(GetProcessHeap(), 0, pHandleInfo);
return;
}
DWORD queryObj(LPVOID lpParam)
{
return NtQueryObject(hCopy, 1, pObject, MAX_PATH * 2, NULL);
}
void getFileName(string& fileName)
{
pObject = (POBJECT_NAME_INFORMATION)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * 2);
if (pObject == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
HANDLE hThread = CreateThread(NULL, 0, queryObj, NULL, 0, NULL);
if (hThread == 0)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
DWORD dwSatus = WaitForSingleObject(hThread, 200);
if (dwSatus == WAIT_TIMEOUT)
{
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
if (pObject->NameBuffer != NULL)
{
DWORD n = WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, NULL, 0, NULL, FALSE);
char* name = new char[n + 1];
memset(name, 0, n + 1);
WideCharToMultiByte(CP_OEMCP, NULL, pObject->NameBuffer, -1, name, n, NULL, FALSE);
fileName = name;
delete[] name;
HeapFree(GetProcessHeap(), 0, pObject);
return;
}
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-9-18 11:07
被庄周の蝴蝶编辑
,原因: 格式调整