如何在C语言中检查文件是否已被其他进程打开?

15

我看到标准C无法判断文件是否在另一个进程中已经打开。因此,答案应该包含每个平台的多个示例。尽管如此,我需要在Visual C++ / Windows中进行检查。


为什么您想要确定文件是否被其他进程打开?另外,您是想要确定文件是否被特定(即已知)进程打开,还是只需确定是否被任何其他进程打开? - Dale Hagglund
1
我对“哪个”进程正在访问信息不感兴趣。这个问题之所以出现在这里,是因为其他平台上似乎没有人回答这个问题,但可能有必要知道这个问题的答案。所以请简短明了地回答,不要告诉我“重新思考”这个问题。想想资源管理器,就好像在删除文件之前,它会检查是否有其他进程正在访问它。 - winlin
2
根据你的回答,你遇到了文件锁定问题,每个平台都有明确定义的解决方法。至于不想重新思考这个问题,我只想指出将问题从“谁打开了文件”改为“如何锁定文件”就是重新思考问题,并将其从一个非常困难的问题变成了一个简单得多的问题。 - Dale Hagglund
不是同一个应用程序。我正在修改另一个进程的文件。 - winlin
1
“每个平台”?!?!你知道有多少个平台吗? - twalberg
显示剩余2条评论
8个回答

14

Windows:尝试以独占模式打开文件。如果成功,表示没有其他人打开该文件,也无法打开该文件。

HANDLE fh;
fh = CreateFile(filename, GENERIC_READ, 0 /* no sharing! exclusive */, NULL, OPEN_EXISTING, 0, NULL);
if ((fh != NULL) && (fh != INVALID_HANDLE_VALUE))
{
   // the only open file to filename should be fh.
   // do something
   CloseHandle(fh);
}

微软公司表示:dwShareMode。

对象的共享模式,可以是读、写、两者皆有、删除、所有这些或者没有(请参考下表)。

如果此参数为零并且CreateFile成功,则该对象无法共享,直到关闭句柄之前也无法再次打开。

您不能请求与具有打开句柄的打开请求中指定的访问模式冲突的共享模式,因为这会导致以下共享违规:ERROR_SHARING_VIOLATION。

http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx

扩展内容: 如何删除一个没有任何人以读/写方式打开的文件系统中的(非只读)文件?

需要访问权限文件 FILE_READ_ATTRIBUTES,而不是删除权限。DELETE 权限可能会在 smb 共享(对于 MS Windows 服务器)上引起问题——CreateFile 将保留仍然打开的 FileHandle /Device/Mup:xxx 文件名,不管 Mup 是什么意思。如果使用文件访问权限 FILE_READ_ATTRIBUTES,就不会出现这种情况。 使用 FILE_FLAG_OPEN_REPARSE_POINT 删除文件名。否则,您将会删除符号链接的目标——这通常不是您想要的。

HANDLE fh;
fh = CreateFile(filename,  FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE /* no RW sharing! */, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_DELETE_ON_CLOSE, NULL);
if ((fh != NULL) && (fh != INVALID_HANDLE_VALUE))
{
    DeleteFile(filename); /* looks stupid?
                          * but FILE_FLAG_DELETE_ON_CLOSE will not work on some smb shares (e.g. samba)!
                          * FILE_SHARE_DELETE should allow this DeleteFile() and so the problem could be solved by additional DeleteFile()
                          */
    CloseHandle(fh); /* a file, which no one has currently opened for RW is delete NOW */

} 

如何处理一个已经打开的文件?如果这个文件已经被打开,而你又有unlink权限,那么你会留下一个文件,后续的打开操作将导致ACCESS_DENIED错误。如果你有一个临时目录,那么将filename重命名为tempdir/filename.delete并删除tempdir/filename.delete可能是一个好主意。


9

除非其他进程明确禁止访问文件,否则无法确定。在MSVC中,您可以使用_fsopen(),并将_SH_DENYRD指定为shflag参数来实现。在多任务操作系统上,关注是否打开了未锁定的文件的概念是深度错误的。它可能会在您发现它没有被锁定的一微秒后被打开。这也是Windows没有IsFileLocked()函数的原因。

如果您需要对文件进行同步访问,则需要使用命名互斥对象,使用CreateMutex()函数。


谢谢。对于Windows,这个方法很有效并且真的解决了我的问题。其他平台的用户可能需要寻找其他答案。 - winlin

5
获取open_files信息非常困难,就像拔牙一样,如果你没有紧急需要,你不应该为了好奇而要求“每个平台的几个示例”。当然,这只是我的观点。
Linux和许多Unix系统有一个名为lsof的系统实用程序,可以找到打开的文件句柄等内容。它的工作方式是通过访问/dev/kmem,这是一个伪文件,其中包含“实时”内核内存的副本,即操作系统内核的工作存储。自然地,在那里有打开文件的表,内存结构是开源和文档化的,因此对于lsof来说,只是进行大量繁琐的工作,找到信息并将其格式化给用户。
另一方面,Windows的深层文档几乎不存在,我不知道数据结构是否暴露给外部。我不是Windows专家,但除非Windows API明确提供此类信息,否则可能根本无法使用。
任何可用的东西可能都被Mark Russinovich的SysInternals工具使用了;我想到的第一个工具是FileMon。查看这些可能会给你一些线索。更新:我刚刚被告知,SysInternals Handles.exe 更接近你想要的。

如果你设法弄清楚了,那很好;否则,你可能会对捕捉文件打开/关闭操作感兴趣:Windows API提供了大量所谓的Hooks:http://msdn.microsoft.com/en-us/library/ms997537.aspx。 Hooks允许您在系统中发生某些事情时请求通知。我相信有一种方法可以告诉您何时程序-整个系统-打开文件。因此,您可以在您监听挂钩的持续时间内制作自己的文件打开列表。我不确定,但我怀疑这可能就是FileMon所做的事情。

包括hook函数在内的Windows API可以从C中访问。系统范围的钩子将要求您创建一个DLL,以便在您的程序旁边加载。

希望这些提示能帮助您入门。


1
这在Windows系统中是可行的。例如,Sysinternals handle.exe 工具可以实现此功能。http://technet.microsoft.com/en-us/sysinternals/bb896655.aspx - Nick Moore

4

对于Windows,这段代码同样适用:

boolean isClosed(File f) { return f.renameTo(f); }

打开的文件无法更名,以相同的名称重命名不会引起另一个错误。因此,如果重命名成功,实际上没有做任何事情,您就知道该文件未被打开。


这对我很有效,而且实现起来非常简单!它是用Java编写的,而不是C,这也许是你被投票降级的原因。我发现这个答案非常有帮助,谢谢。 - Rich
1
你正在使用哪个库来执行重命名操作?因为如果我尝试使用std::rename重命名文件,Windows会允许重命名该文件,即使它已经被打开了。 - Angie Quijano

2
任何这样的检查都会天生具有竞争性。在您进行检查和访问文件之间的时间点,另一个进程总是可以打开该文件。

1
到目前为止,答案应该告诉你,找到你所询问的信息是棘手的、不可移植的,并且通常本质上是不可靠的。因此,从我的角度来看,真正的答案是不要这样做。试着想办法思考你的实际问题,以便不会出现这个问题。

0
你可以使用类似这样的东西。虽然不是一个完美的解决方案,但它能够工作。
 bool IsFileDownloadComplete(const std::wstring& dir, const std::wstring& fileName) 
    {
        std::wstring originalFileName = dir + fileName;
        std::wstring tempFileName = dir + L"temp";

        while(true)
        {
            int ret = rename(convertWstringToString(originalFileName).c_str(), convertWstringToString(tempFileName).c_str());
            if(ret == 0)
                break;      

            Sleep(10);
        }   

        /** File is not open. Rename to original. */
        int ret = rename(convertWstringToString(tempFileName).c_str(), convertWstringToString(originalFileName).c_str());
        if(ret != 0)
            throw std::exception("File rename failed"); 

        return true;
    }

0

这个应该不难吧,伙计们。

做这个:

try{
File fileout = new File(path + ".xls");
FileOutPutStream out = new FileOutPutStream(fileout);
}
catch(FileNotFoundException e1){

 // if a MS Windows process is already using the file, this exception will be thrown
}
catch(Exception e){

}

真的,哈哈抱歉 - Alexander Mills
你正在使用哪个版本的C++? - MARSHMALLOW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接