How to list files in a directory using the Windows API?

30.4k views Asked by At

I have this code and it displays the folder with the directory itself and not its contents. I want to display its contents. I don't want to use boost::filesystem.

How can I resolve this?

Code:

#include <windows.h>
#include <iostream>

int main()
{
    WIN32_FIND_DATA data;
    HANDLE hFind = FindFirstFile("C:\\semester2", &data);      // DIRECTORY

    if ( hFind != INVALID_HANDLE_VALUE ) {
        do {
            std::cout << data.cFileName << std::endl;
        } while (FindNextFile(hFind, &data));
        FindClose(hFind);
    }
}

Output:

semester2
4

There are 4 answers

6
Harry Johnston On BEST ANSWER
HANDLE hFind = FindFirstFile("C:\\semester2", &data);       // DIRECTORY

You got the directory because that's what you asked for. If you want the files, ask for them:

HANDLE hFind = FindFirstFile("C:\\semester2\\*", &data);  // FILES

(You can instead use *.* if you prefer, but apparently this only works because of a backwards compatibility hack so should probably be avoided. See comments and RbMm's answer.)

1
papadp On

Harrys answer will actually yield files and folders having an extension in your desired folder "C:\\semester2".

So for example if you have a folder named "C:\\semester2\\math.course" it will also be found by the above example. Moreover if you will have a file named "C:\\semester2\\math_scores" (notice it not having an extension) it will not be found.

Taking the above into consideration I would suggest the following solution:

HANDLE hFind = FindFirstFile("C:\\semester2\\*", &data); 

This will list the entire listing of items under the directory. Filtering the directories can be done in the following way:

if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// directory
}
else
{
// file
}

The following can be used for references: FileAttributes constants, FIND_DATA struct, FindFirstFile API

5
RbMm On

Let me take some notes about "*.*" vs "*". These filers are not equal.

2 different files can exist in our folder: somefile and somefile..

If we used the low level api ZwQueryDirectoryFile with "*.*" as a search expression (this is the 10th parameter - FileName [in, optional] ) - we would get somefile. only. But if we used "*" we'd get both files - somefile and somefile.

If we try FindFirstFile("C:\\semester2\\*.*", &data); we can note than both files somefile and somefile. are returned. So here "*.*" vs "*" have the same effect - no difference in usage.

Why does this happen? Because inside FindFirstFileEx in kernelbase (kernel32 ) do special check for "*.*" mask and if it true - replace to "" (An empty name which have the same effect as "*" ).

I think this is done to fix a very common error when users pass "*.*" instead of the correct "*" and for backward compatability with legacy code.

. and .. aren't actually part of the directory as it is stored on disk, but are added by the Win32 API.

This is not true.

  • for FAT-style file system this is really stored on FAT directory as 2 first entry.
  • in NTFS there are no such entries, but NTFS.sys artificially add this 2 entries if they in mask.

So this is done not at Win32 API level, but in kernel - on driver level.

In conclusion, "*.*" will work correct with Win32 API how minimum now - but the correct and clean way is to use "*" here.
"*.*" will be mistake with ZwQueryDirectoryFile api.

2
Ferdi Kedef On

Here is an example implementation:

#include <iostream>
#include <vector>
#include <string>
#include <Windows.h>

std::vector<std::string>
list_directory(
    const std::string &directory)
{
    WIN32_FIND_DATAA findData;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    std::string full_path = directory + "\\*";
    std::vector<std::string> dir_list;

    hFind = FindFirstFileA(full_path.c_str(), &findData);

    if (hFind == INVALID_HANDLE_VALUE)
        throw std::runtime_error("Invalid handle value! Please check your path...");

    while (FindNextFileA(hFind, &findData) != 0)
    {
        dir_list.push_back(std::string(findData.cFileName));
    }

    FindClose(hFind);

    return dir_list;
}

Note: It would be much better to use something like boost::filesystem if your using C++ 11 or std::filesystem if your using C++ 17. Also the input path has to be like C:\path and not C:\path\ otherwise this wont work!!