C++ 确定两个路径是否引用Windows中的同一文件的最佳方法?

C++ 确定两个路径是否引用Windows中的同一文件的最佳方法?,c++,winapi,path,C++,Winapi,Path,在使用C/C++的Win32中,如何比较两个字符串以确定它们是否引用相同的路径 虽然这将处理很多情况,但它忽略了一些事情: _tcsicmp(szPath1, szPath2) == 0 例如: 正斜杠/反斜杠 相对/绝对路径 [编辑]更改标题以匹配现有的C#问题。简单的字符串比较不足以比较路径是否相等。在windows中,c:\foo\bar.txt和c:\temp\bar.txt很可能通过文件系统中的符号链接和硬链接指向完全相同的文件 正确地比较路径实际上迫使您打开这两个文件并比较低级

在使用C/C++的Win32中,如何比较两个字符串以确定它们是否引用相同的路径

虽然这将处理很多情况,但它忽略了一些事情:

_tcsicmp(szPath1, szPath2) == 0
例如:

  • 正斜杠/反斜杠

  • 相对/绝对路径


[编辑]更改标题以匹配现有的C#问题。

简单的字符串比较不足以比较路径是否相等。在windows中,c:\foo\bar.txt和c:\temp\bar.txt很可能通过文件系统中的符号链接和硬链接指向完全相同的文件

正确地比较路径实际上迫使您打开这两个文件并比较低级句柄信息。任何其他方法都会产生不稳定的结果

看看这篇卢西恩关于这个主题的精彩文章。代码是用VB编写的,但它可以很好地翻译成C/C++语言,就像他对大多数方法所做的那样


使用kernel32.dll中的GetFullPathName,这将为您提供文件的绝对路径。然后使用一个简单的字符串比较将其与您拥有的其他路径进行比较

编辑:代码

TCHAR buffer1[1000];
TCHAR buffer2[1000];
TCHAR buffer3[1000];
TCHAR buffer4[1000];

GetFullPathName(TEXT("C:\\Temp\\..\\autoexec.bat"),1000,buffer1,NULL);
GetFullPathName(TEXT("C:\\autoexec.bat"),1000,buffer2,NULL);
GetFullPathName(TEXT("\\autoexec.bat"),1000,buffer3,NULL);
GetFullPathName(TEXT("C:/autoexec.bat"),1000,buffer4,NULL);
_tprintf(TEXT("Path1: %s\n"), buffer1);
_tprintf(TEXT("Path2: %s\n"), buffer2);
_tprintf(TEXT("Path3: %s\n"), buffer3);
_tprintf(TEXT("Path4: %s\n"), buffer4);
上述代码将为所有三种路径表示打印相同的路径。。在此之后,您可能需要执行不区分大小写的搜索参见以下问题:


问题是关于C#,但答案只是Win32 API调用
GetFileInformationByHandle

如果引用UNC或规范路径(即本地路径以外的任何路径),比较实际路径字符串将不会产生准确的结果

在确定路径是否相同时,shlwapi.h中有一些可能对您有用


它包含可以在更大范围的函数中使用的函数。

使用
CreateFile
打开两个文件,调用
GetFileInformationByHandle
,并比较
dwVolumeSerialNumber
nFileIndexLow
nFileIndexHigh
。如果三者都相等,则它们都指向同一个文件:

根据关于的答案,以下是代码

注意:仅当文件已存在时,此操作才有效…

//Determine if 2 paths point ot the same file...
//Note: This only works if the file exists
static bool IsSameFile(LPCWSTR szPath1, LPCWSTR szPath2)
{
    //Validate the input
    _ASSERT(szPath1 != NULL);
    _ASSERT(szPath2 != NULL);

    //Get file handles
    HANDLE handle1 = ::CreateFileW(szPath1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
    HANDLE handle2 = ::CreateFileW(szPath2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 

    bool bResult = false;

    //if we could open both paths...
    if (handle1 != INVALID_HANDLE_VALUE && handle2 != INVALID_HANDLE_VALUE)
    {
        BY_HANDLE_FILE_INFORMATION fileInfo1;
        BY_HANDLE_FILE_INFORMATION fileInfo2;
        if (::GetFileInformationByHandle(handle1, &fileInfo1) && ::GetFileInformationByHandle(handle2, &fileInfo2))
        {
            //the paths are the same if they refer to the same file (fileindex) on the same volume (volume serial number)
            bResult = fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber &&
                      fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh &&
                      fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow;
        }
    }

    //free the handles
    if (handle1 != INVALID_HANDLE_VALUE )
    {
        ::CloseHandle(handle1);
    }

    if (handle2 != INVALID_HANDLE_VALUE )
    {
        ::CloseHandle(handle2);
    }

    //return the result
    return bResult;
}

您需要做的是获取规范路径

对于您拥有的每个路径,请文件系统转换为规范路径,或为您提供唯一标识文件的标识符(例如iNode)

然后比较规范路径或唯一标识符

注:
不要试图自己找出锥形路径文件系统可以通过符号链接等做一些事情,除非您非常熟悉文件系统,否则这些事情不容易处理。

如果文件存在,并且您可以处理打开文件可能带来的争用情况和性能影响,在任何平台上都可以使用的一种不完美的解决方案是,打开一个文件进行单独写入,关闭它,然后在打开另一个文件进行写入后再次打开它进行写入。由于写入访问权限只能是独占的,如果您第一次可以打开第一个文件进行写入,但第二次不能,那么在尝试打开两个文件时,您可能会阻止自己的请求


(当然,系统的其他部分也可能打开了您的一个文件)

如果您可以访问Boost库,请尝试

bool boost::filesystem::path::equivalent( const path& p1, const path& p2 )


从文档中总结:如果给定的
路径
对象解析为同一文件系统实体,则返回
true
,否则返回
false

打开两个文件,并对
句柄使用。然后比较路径。

文件系统库 由于C++17,您可以使用。使用
包含它#包含
。即使在较旧版本的C++中,也可以访问它,请参阅脚注。 您正在查找的函数是命名空间
std::filesystem
下的等效函数:

bool std::filesystem::equivalent(const std::filesystem::path& p1, const filesystem::path& p2 );
总结如下:此函数将两个路径作为参数,如果它们引用相同的文件或目录,则返回true,否则返回false。还有一个采用第三个参数的
noexcept
重载:保存任何可能错误的
std::error\u code

例子 在C++17之前使用文件系统 要在C++17之前的版本中使用该库,您必须在编译器中启用实验语言功能,并以以下方式包含该库:
#include
。然后可以在命名空间
std::experimental::filesystem
下使用它的函数。请注意,实验文件系统库可能不同于C++17。请参阅文档。
例如:

#include <experimental/filesystem>
//...
std::experimental::filesystem::equivalent(p1, p2);
#包括
//...
std::实验::文件系统::等效(p1,p2);

这不起作用,因为同一文件可以有多个路径是,但GetFullPathName将为您提供同一文件的绝对路径。。所以应该是一样的..硬链接允许一个文件有两个不同的名称。是的,你们这些只知道对自己和他人构成危险的人,Windows确实支持硬链接。@IntegerPoet就是这么说的。硬链接是魔鬼的游乐场。你需要的不仅仅是一个路径来确定相同性。这似乎也不能解释带有尾部斜杠的目录(即c:\blah vs c:\blah)。请注意,文件应该保持打开状态,否则可能是相同的数字对应不同的文件。2016年的BY_HANDLE_FILE_信息结构文档中说“不保证此结构中的64位标识符在引用上是唯一的。”和“要检索128位文件标识符,请使用带有FileIdInfo的GetFileInformationByHandleEx函数检索文件\u ID\u信息结构。“这种方法适用于Windows 8及更高版本。它需要编译选项-D_WIN32_WINNT=_WIN32_WINNT_WIN8。即使在本地文件系统上关闭句柄,索引号(inode)也将保持不变。网络驱动器只有一个问题
1
#include <experimental/filesystem>
//...
std::experimental::filesystem::equivalent(p1, p2);