Windows端判断文件是否被独占的方法C/C++

前言

最近开发相关功能的时候,涉及到了文件资源被占用的问题。这里做一下记录

情景再现

我有一个文件资源的对象FileRes,它有唯一对应的一个文件File,此时我有一个图片的变量var,使用了File文件,于是乎,var手握File的文件句柄。

现在有个操作,需要删除FileRes对象,并且将对应的文件也删掉,此时var占据着File的文件句柄,删除肯定是失败的,所以要先判断这个文件是否被占有,如果占有了,我们要提示删除失败,如果没有被占有,才删除。

解决方案

因为涉及到Windows的API,不是很熟,所以就是网上找了一大堆,最后可算是找到了一个,这里列举了两个方案,但是在我的应用场景里面,只有方案一可行,具体原因见下文

方案一:

#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/locking.h>
#include <share.h>
#include <fcntl.h>

bool IsFileUsed(const char* filePath)
{
    bool ret = false;
    int fh = _sopen(filePath, _O_RDWR, _SH_DENYRW, _S_IREAD | _S_IWRITE );
    if(-1 == fh)
        ret = true;    
    else
        _close(fh);
    return ret;
}

这段代码的功能是判断指定的文件是否正在被使用(被其他进程锁定或打开)。

包含的头文件,这些都是 Windows 平台下的系统头文件,提供了文件操作相关的函数和常量定义。
核心函数是IsFileUsed,它的工作原理如下:

函数尝试以_sopen函数打开指定路径的文件
参数_O_RDWR表示以读写方式打开
参数_SH_DENYRW是关键,它表示拒绝其他任何进程对该文件的读写访问
参数_S_IREAD | _S_IWRITE设置文件的读写权限
判断文件是否被使用的逻辑:
如果_sopen返回 - 1,表示文件打开失败,失败的可能原因是文件正在被其他进程使用(已被锁定或打开)
这时函数返回true,表示文件正在被使用

如果成功打开文件,则立即用_close关闭它,并返回false,表示文件未被使用

这段代码利用了 Windows 文件系统的锁定机制:如果一个文件已经被其他进程打开且没有共享权限,那么尝试以独占方式打开它就会失败。通过这种方式间接判断文件是否正在被使用。

方案二:

bool IsFileLocked(const char* filePath)
{
    HANDLE hFile = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        // 文件打开失败,说明文件被独占
        return true;
    }
    else
    {
        // 文件打开成功,关闭文件句柄
        CloseHandle(hFile);
        return false;
    }
}

这段代码定义了一个名为IsFileLocked的函数,用于判断指定的文件是否被其他进程锁定(处于无法共享访问的状态)。它使用了Windows API来实现这一功能,具体解释如下:

函数的工作原理基于Windows文件系统的共享访问机制:

  1. 使用CreateFileA函数尝试打开目标文件

    • 第一个参数filePath:要检查的文件路径
    • 第二个参数GENERIC_READ:以只读方式打开文件
    • 第三个参数是关键:FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE表示允许其他进程同时对该文件进行读、写和删除操作
    • 第四个参数NULL:使用默认的安全属性
    • 第五个参数OPEN_EXISTING:只打开已存在的文件
    • 第六个参数FILE_ATTRIBUTE_NORMAL:使用常规文件属性
    • 最后一个参数NULL:没有模板文件
  2. 判断逻辑:

    • 如果CreateFileA返回INVALID_HANDLE_VALUE(无效句柄),说明文件打开失败
    • 失败的主要原因通常是文件被其他进程以独占方式打开(不允许共享),此时函数返回true表示文件被锁定
    • 如果成功打开文件,函数会立即用CloseHandle关闭文件句柄,并返回false表示文件未被锁定

需要注意的是,这个函数判断的是"文件是否被独占锁定",而不是物理意义上的锁定。它通过尝试以最大共享权限打开文件来推断文件是否被其他进程独占使用。

与前一个使用_sopen的实现相比,这个版本直接使用了Windows API,功能上更为接近系统底层,同样也是Windows平台特有的实现。

方案比较

这两段代码虽然都是用于判断文件是否被占用,但在实现方式、判断逻辑和细节行为上有明显区别,主要差异如下:

1. 底层实现方式不同

  • 第一段代码(IsFileUsed
    使用C运行时库函数 _sopen(微软扩展函数),属于标准C库的扩展,封装了底层系统调用。

  • 第二段代码(IsFileLocked
    直接调用Windows系统API CreateFileA,属于更底层的系统接口,是Windows平台特有的文件操作函数。

2. 判断逻辑的核心差异

  • 第一段代码
    尝试以 _SH_DENYRW(拒绝其他进程读写) 的独占模式打开文件。

    • 如果打开失败(返回-1),说明文件已被其他进程占用(因为无法获取独占权限),返回true
    • 本质是:“我能否独占这个文件?不能则表示文件被使用”
  • 第二段代码
    尝试以 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE(允许所有共享) 的最大共享模式打开文件。

    • 如果打开失败(返回INVALID_HANDLE_VALUE),说明文件被其他进程以独占方式锁定(不允许任何共享),返回true
    • 本质是:“我能否以最宽松的共享方式打开文件?不能则表示文件被独占锁定”

3. 对“文件被占用”的判断范围不同

  • 第一段代码的判断更宽泛:
    只要文件无法以独占方式打开(包括被其他进程以非独占方式打开但占用了部分权限),就会判定为“被使用”。例如,其他进程以“允许读但禁止写”的方式打开文件时,_sopen会失败。

  • 第二段代码的判断更严格:
    只有当文件被其他进程以完全独占(不允许任何共享)的方式打开时,才会判定为“被锁定”。如果其他进程以“允许共享读”的方式打开文件,CreateFileA仍能成功打开,返回false

4. 适用场景不同

  • IsFileUsed:适合判断文件是否“正在被任何进程访问”(包括非独占访问),例如检查文件是否被编辑中。

  • IsFileLocked:适合判断文件是否“被严格锁定”(如被进程独占写入),例如检查文件是否被锁定以防止任何修改。

两段代码的核心区别在于打开文件时请求的共享权限相反

  • IsFileUsed 尝试独占文件,失败则认为文件被使用;
  • IsFileLocked 尝试最大程度共享文件,失败则认为文件被严格锁定。
    选择哪段代码取决于需要判断的“文件被占用”的严格程度。

总结

方案二我自己尝试为什么不行,其实就在于文件资源File只是被占用,并且是以非独占的方式访问,所以就导致第二种方式获取到的判定结果和我预想的不一致


澄澈i
用简单的语言记录自己走过的技术路

<think>我们正在解决如何用C++判断一个文件是否被其他应用程序打开的问题。由于操作系统不同,方法也不同。这里我们主要讨论Windows和Linux平台。在Windows上,我们可以尝试以独占模式打开文件。如果成功,说明没有其他进程占用;如果失败,则可能被占用。在Linux上,我们可以使用lsof命令或者检查/proc文件系统,但要注意权限问题。但是,需要注意的是,即使我们能够检测到文件是否被其他进程打开,也不能保证在检测之后立即被其他进程打开或关闭(竞态条件)。因此,这种检测通常用于避免冲突,但程序本身应该能够处理打开失败的情况。下面分别提供Windows和Linux下的示例代码:Windows平台:使用CreateFile函数,指定dwShareMode为0(独占模式),如果打开失败并返回ERROR_SHARING_VIOLATION,则说明文件被其他进程占用。Linux平台:我们可以尝试通过读取/proc文件系统来获取所有打开的文件信息,然后检查目标文件是否在其中。但这种方法比较繁琐,且不同Linux发行版的/proc结构一致。另一种方法是使用lsof命令,但需要确保系统安装了该工具。然而,在Linux下,更直接的方法是尝试以独占模式打开(使用flock或fcntl),但注意Linux下默认的文件锁是建议锁(advisory),所以不一定可靠。另一种方法是使用open()函数并指定O_EXCL标志,但O_EXCL通常只用于创建文件(即与O_CREAT一起使用)时防止重复创建。因此,在Linux下,我们可以尝试以读写方式打开文件,然后尝试加一个非阻塞的独占锁,如果加锁成功,则说明没有其他进程占用;如果失败,则可能被占用。注意:这种方法依赖于其他进程也使用文件锁,如果其他进程不使用文件锁,则无法检测。由于问题的复杂性,我们这里提供两种不同平台的示例代码:1.Windows示例:```cpp#include<windows.h>#include<tchar.h>boolIsFileLocked(conststd::wstring&filePath){HANDLEhFile=CreateFile(filePath.c_str(),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);if(hFile==INVALID_HANDLE_VALUE){if(GetLastError()==ERROR_SHARING_VIOLATION)returntrue;//文件被其他进程占用//其他错误,可能是文件不存在等returnfalse;}CloseHandle(hFile);returnfalse;//文件没有被占用}```2.Linux示例(使用文件锁):注意:这种方法要求其他进程也使用文件锁,否则无法检测。```cpp#include<sys/file.h>#include<fcntl.h>#include<unistd.h>boolIsFileLocked(conststd::string&filePath){intfd=open(filePath.c_str(),O_RDONLY);if(fd<0){//文件打开失败,可能是文件不存在或其他错误returnfalse;}//尝试获取非阻塞的独占锁if(flock(fd,LOCK_EX|LOCK_NB)==-1){//如果errno为EWOULDBLOCK,则表示文件已经被锁定close(fd);returntrue;}//解锁并关闭文件flock(fd,LOCK_UN);close(fd);returnfalse;}```但是,Linux下的flock锁是劝告锁,如果其他进程不使用flock,则不会发生冲突。因此,这种方法并不完全可靠。替代方案:在Linux下,我们可以通过检查/proc文件系统来查看哪些进程打开了该文件。这种方法不需要其他进程使用锁,但需要遍历/proc下的所有进程目录,检查其文件描述符。这种方法可能会受到权限限制(需要root权限才能查看所有进程的文件描述符),且效率较低。下面是一个通过/proc检查的示例(仅适用于Linux):```cpp#include<dirent.h>#include<cstring>#include<fstream>#include<string>#include<vector>#include<sys/stat.h>//获取文件的绝对路径std::stringGetAbsolutePath(conststd::string&path){char*abs_path=realpath(path.c_str(),nullptr);if(abs_path==nullptr){return"";}std::stringresult(abs_path);free(abs_path);returnresult;}//检查文件是否被其他进程打开boolIsFileOpenedByOtherProcesses(conststd::string&filePath){std::stringabsPath=GetAbsolutePath(filePath);if(absPath.empty()){returnfalse;//文件不存在}DIR*dir=opendir("/proc");if(!dir){returnfalse;}structdirent*entry;while((entry=readdir(dir))!=nullptr){//跳过非进程目录(进程目录名为数字)if(entry->d_type!=DT_DIR||!isdigit(entry->d_name[0]))continue;std::stringpid=entry->d_name;std::stringfdPath="/proc/"+pid+"/fd";//打开进程的文件描述符目录DIR*fdDir=opendir(fdPath.c_str());if(!fdDir){continue;}structdirent*fdEntry;while((fdEntry=readdir(fdDir))!=nullptr){if(fdEntry->d_type==DT_LNK){std::stringfullFdPath=fdPath+"/"+fdEntry->d_name;charlinkTarget[1024];ssize_tlen=readlink(fullFdPath.c_str(),linkTarget,sizeof(linkTarget)-1);if(len!=-1){linkTarget[len]='\0';if(absPath==linkTarget){closedir(fdDir);closedir(dir);returntrue;}}}}closedir(fdDir);}closedir(dir);returnfalse;}```注意:这种方法需要遍历整个/proc,可能会比较慢,而且需要足够的权限(有些进程的fd目录需要root权限才能访问)。因此,在实际应用中需要权衡。总结:由于不同平台和不同场景下的需求,检测文件是否被其他应用程序打开是一个复杂的问题。在Windows下,我们可以通过独占模式打开来检测;在Linux下,则没有一种完全可靠且高效的方法。如果可能,最好在程序中直接处理文件打开失败的情况(例如重试或提示用户),而不是依赖事前的检测。因此,建议在需要操作文件时,直接尝试以所需的方式打开文件,并处理可能出现的错误(如Windows的ERROR_SHARING_VIOLATION,Linux的EAGAIN等)。这样比先检测再操作更可靠,因为检测和操作之间可能存在时间差。下面,我们将根据上述内容生成相关问题。</think>### 如何用C++检测文件是否被其他应用程序打开 在C++中检测文件是否被其他进程占用,通常需要针对不同操作系统使用特定API。以下是常见解决方案: #### **Windows系统实现** ```cpp #include <windows.h> #include <iostream> bool IsFileLocked(const wchar_t* filePath) { HANDLE hFile = CreateFileW( filePath, GENERIC_READ, 0, // 独占模式:禁止其他进程读写 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); // 共享冲突错误码为32 (ERROR_SHARING_VIOLATION) if (err == ERROR_SHARING_VIOLATION) return true; // 文件占用 } else { CloseHandle(hFile); } return false; // 文件未被占用 } // 使用示例 int main() { if (IsFileLocked(L"C:\\test.txt")) { std::cout << "文件被其他进程占用" << std::endl; } else { std::cout << "文件可访问" << std::endl; } } ``` #### **Linux/macOS系统实现** ```cpp #include <fcntl.h> #include <unistd.h> #include <iostream> bool IsFileLocked(const char* filePath) { int fd = open(filePath, O_RDONLY | O_EXCL); if (fd == -1) { // EACCES: 权限不足, EAGAIN: 非阻塞模式冲突 if (errno == EACCES || errno == EAGAIN) return true; // 文件占用 } else { close(fd); } return false; } // 使用示例 int main() { if (IsFileLocked("/tmp/test.txt")) { std::cout << "文件被其他进程占用" << std::endl; } else { std::cout << "文件可访问" << std::endl; } } ``` #### **关键说明** 1. **Windows独占模式原理** 通过`CreateFileW`的`dwShareMode=0`参数请求独占访问权,若失败且错误码为`ERROR_SHARING_VIOLATION`(32),则表明文件已被占用[^1]。 2. **Linux O_EXCL标志** 使用`open()`的`O_EXCL`标志确保检查操作的原子性,避免竞态条件。 3. **跨平台注意事项** - Windows需使用宽字符路径(`L"path"`) - Linux需包含`<errno.h>`处理错误码 - 权限问题可能导致误判(如无读权限) 4. **可靠性限制** 检测结果仅表示**当前时刻**文件状态,其他进程可能在检测后立即访问文件。 #### **替代方案建议** - 直接尝试以**读写模式**打开文件并处理异常 - 使用平台特定API如Windows的`Restart Manager`[^2] - 第三方库如`Boost.Filesystem`(但无直接检测函数) > 在实际应用中,推荐优先处理文件操作时的异常(如`std::ifstream::open()`失败),而非预先检测占用状态[^3]。 --- ### 相关问题 1. **Windows下如何获取占用文件的进程ID?** [需要调用`NtQuerySystemInformation`等底层API或使用`Restart Manager`接口[^2]] 2. **Linux中如何实现递归检测文件夹内所有文件占用状态?** [需结合`opendir/readdir`遍历目录和`O_EXCL`检测] 3. **C++文件操作中如何处理"文件占用"的异常?** [需区分操作系统错误码并实现重试机制] 4. **跨平台文件锁检测库有哪些推荐?** [可评估`Qt`的`QFile`或`POCO`库的`File`类实现方案] 5. **如何避免文件占用检测中的竞态条件?** [建议在检测后立即进行文件操作,或使用原子操作替代二次检测]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

澄澈i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值