0

I have a small piece of code that is meant to list the contents of a directory using the Windows API.

CODE:

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

int wmain() {

    WIN32_FIND_DATA data;
    std::string dir = "c:\\* "; 
    HANDLE hFind = FindFirstFileA(dir.c_str(), &data);      // DIRECTORY

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

Error:

Error C2664 'HANDLE FindFirstFileA(LPCSTR,LPWIN32_FIND_DATAA)': cannot convert argument 2 from 'WIN32_FIND_DATA *' to 'LPWIN32_FIND_DATAA'

The errors change depending on my attempts at fixes. I have tried the following:

  • Tried to change FindFirstFile to FindFirstFileW / FindNextFileA
  • Tried to put an L in front of directory string L"C:\\*"

I have looked at the following related questions:

But did not get it to successfully list the contents. Closest I have got is this code which compiles without errors:

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

int main()
{
    WIN32_FIND_DATA data;
    HANDLE hFind = FindFirstFileW(L"C:\\*", &data);

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

This compiles but when I run the binary, it outputs this:

0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C 0000009A9DBAF32C

While we are at it, what's the difference between FindNextFile, FindNextFileA and FindNextFileW? I feel like the issue is somehow related to these?

learnerX
  • 1,022
  • 1
  • 18
  • 44

2 Answers2

4

FindNextFile is a macro that resolves to either FindNextFileA (ANSI) or FindNextFileW (Unicode UTF16-LE) depending on whether UNICODE is defined (and _UNICODE).

So you change WIN32_FIND_DATA to WIN32_FIND_DATAA as FindFirstFileA needs the ANSI version of the parameter.

See https://devblogs.microsoft.com/oldnewthing/20040212-00/?p=40643 for an explanation of the differences between defining UNICODE and _UNICODE

Richard Critten
  • 2,138
  • 3
  • 13
  • 16
  • 1
    To be clear, while this is the solution to make `FindFirstFileA` work, it is not the correct solution in general. It's 2019, write Unicode friendly code. As Hans said in the question comments, the correct solution is to use the `W` versions of the APIs (or the unsuffixed versions, but making sure your build environment defines `UNICODE`/`_UNICODE`, which it should by default on modern versions of Visual Studio), and just send your output to `std::wcout` which supports wide character output. – ShadowRanger Jun 12 '19 at 17:13
  • _Or_, if the rest of your application is nice and UTF-8, convert to UTF-8 on the way out of this function so you can just use `std::cout` to stream the constituent bytes verbatim to your UTF-8 capable terminal. – Lightness Races in Orbit Jun 13 '19 at 22:56
3

In your first code snippet, you are clearly compiling your project with UNICODE defined (as evident by you being able to pass a WIN32_FIND_DATA to FindNextFileW() without error). When UNICODE is defined, WIN32_FIND_DATA maps to WIN32_FIND_DATAW. But FindFirstFileA() wants a WIN32_FIND_DATAA instead. So the simpliest solution to that error is to just change WIN32_FIND_DATA to WIN32_FIND_DATAA to satisfy the call to FindFirstFileA(). But then you will have a new error because you would be passing a WIN32_FIND_DATAA to FindNextFileW(), which wants a WIN32_FIND_DATAW instead. So you will need to call FindNextFileA() instead to fix that error:

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

int wmain() {

    WIN32_FIND_DATAA data;
    std::string dir = "c:\\* "; 
    HANDLE hFind = FindFirstFileA(dir.c_str(), &data);      // DIRECTORY

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

In your second code snippet, std::cout does not have an overloaded operator<< for wchar_t data, but it does have an overload for void*. The WIN32_FIND_DATAW::cFileName field is a wchar_t[] array, which decays into a wchar_t* pointer to the 1st character, and all pointers are implicitly convertible to void*. So that is why you are seeing memory addresses being printed. You need to use std::wcout instead:

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

int wmain()
{
    WIN32_FIND_DATAW data;
    std::wstring dir = L"c:\\*";
    HANDLE hFind = FindFirstFileW(dir.c_str(), &data);

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

Most Win32 APIs that deal with string data have A (ANSI) and W (Wide, Unicode) flavors, and provide generic TCHAR-based macros to map to one or the other based on the UNICODE conditional. In this case, FindFirstFile() maps to either FindFirstFileA() or FindFirstFileW(), FindNextFile() maps to either FindNextFileA() or FindNextFileW(), WIN32_FIND_DATA map to either WIN32_FIND_DATAA or WIN32_FIND_DATAW, etc.

So the general rule is, the suffix you explicitly state (or omit) in an API function call should also be repeated in any related variable declarations as needed. If you call an A API function explicitly, use appropriate A variable types explicitly. If you call a W API function explicitly, use appropriate W variable types explicitly. If you omit A/W in an API function call, omit it in variable declarations (or replace with T for TCHAR, if needed).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Good answer. OP needs to choose between char and wide-char, then settle on the same implementation across both the stdlib and WinAPI, rather than randomly mixing & matching / guessing. – Lightness Races in Orbit Jun 13 '19 at 22:55