2

Unfortunately, directory/file names in Windows are case-insensitive.

When I compare a text (input from user) with a directory name (coming from CFileFind), how can I check whether they mean the same directory or not? For example C:\PIPPO\ and C:\Pippo\ are the same directory, while C:\Pippò\ is not the same (the last one has an accent).

I'm trying with:

if(CompareString(LOCALE_INVARIANT,NORM_IGNORECASE,q,-1,data_from_CfileFind->txt.GetBuffer(),-1)==CSTR_EQUAL)

(q is [part of] the user input)

It "kind of works", as it recognizes as same directory the case variations of Roman, Greek and Cyrillic alphabets, but it confuses "weiß" and "weiss" (and they are two different directories on my disc), so it's not reliable.

[the failing test is inspired by Comparing and sorting Unicode filenames : I have read it, but found no suitable solution - the links appear not to work)

(I also read Windows Invariant Culture Puzzle but I'm afraid I didn't fully understand about the "cultures").

Any suggestion?

Maybe I should call CompareString() with different parameters? Or is there a better, easier approach?

Please note that I don't need to sort names: I'd just like to check whether they mean the same directory (or file) to Windows or not.

By "Windows", I mean from 2000 (or at least XP) and later.

EDIT (sorry, the question was not well-asked the first time)

1) The path input by the user is not guaranteed to refer to an actually existing directory (in this case, of course, they are not the same directory).

2) I know that files and directories can be referred to by very different names, because of links (hard or soft), substs, network access with different name or IP to the same computer, etc... but I'm not asking to detect all of those cases. All I'd like to check is whether a name written by the user is a case-variation of another existing one (and thus, for example, Windows would tell me that the file already exists if I try to create one with the same name but different case).

2nd EDIT

This does the job (at least, in the cases that I tried):

if(CompareStringOrdinal(q,-1,data_from_CfileFind->txt.GetBuffer(),-1,1)==CSTR_EQUAL)

But CompareStringOrdinal() is not available in older Windows versions. Is there any equivalent?

GGa
  • 97
  • 4
  • Can this help? https://stackoverflow.com/questions/684684/normalize-file-path-with-winapi – Renat Apr 24 '19 at 22:44

3 Answers3

2

The best way (that I know of) to check if two filesystem paths refer to the same item, without resorting to string comparisons, is to either:

  1. open HANDLEs to the two paths using CreateFile(), then get the unique filesystem IDs from the HANDLEs and compare them for equality. On FAT and NTFS, use the combination of dwVolumeSerialNumber and nFileIndex(Low|High) from GetFileInformationByHandle(). On ReFS, use the combination of VolumeSerialNumber and FileId from GetFileInformationByHandleEx(FileIdInfo) instead. You can use GetVolumeInformation() to detect which filesystem is being used by each path.

    This approach is described in the BY_HANDLE_FILE_INFORMATION and FILE_ID_INFO documentations, respectively:

    The identifier (low and high parts) and the volume serial number uniquely identify a file on a single computer. To determine whether two open handles represent the same file, combine the identifier and the volume serial number for each file and compare them.

    The 128-bit file identifier for the file. The file identifier and the volume serial number uniquely identify a file on a single computer. To determine whether two open handles represent the same file, combine the identifier and the volume serial number for each file and compare them.

  2. a filesystem-agnostic approach is to use SHGetDesktopFolder() to get the IShellFolder interface for the root desktop of the Shell namespace, then resolve both paths to absolute PIDLs using the desktop's IShellFolder::ParseDisplayName() method (or use the standalone SHParseDisplayName() function), and then compare the PIDLs using the desktop's IShellFolder::CompareIDs() method.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I'm afraid I misled you with the question (please see the "EDIT"). However, `CreateFile()` does'n work on some directories (like `C:\System Volume Information` - but I cannot create e.g. a `C:\System VOLUME Information` because it would be the same directory...). I'll try method 2. or somehing simpler. Thank you so much for your answer. – GGa Apr 25 '19 at 10:25
1

Start by calling GetFullPathName on each path, then do GetLongPathName and finally do a case-insensitive comparison on the results.

GetFullPathName will give a fully qualified path to each file. GetLongPathName then gets the real name of each component of that path, so if somebody used a Windows 95/98 style short name for a file/directory, that wont confuse things.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • "and finally do a case-insensitive comparison on the results" is exactly my problem. How do I perform it? I tried with `CompareString(LOCALE_INVARIANT,NORM_IGNORECASE ....` but it confuses `weiß` and `weiss` that are two different directories. – GGa Apr 25 '19 at 09:52
0

That's the way it appears to work:

1) At the start of the program, call a setlocale(LC_CTYPE, "");

2) Then compare the strings with if(!data_from_CfileFind->txt.CompareNoCase(q)) (that calls _wcsicmp that calls _wcsicmp_l)

GGa
  • 97
  • 4