2

I try to open existing file via CreateFile, but it is always failing with errorcode 2 - like file doesn't exist, but it exists - it's in folder with executable.

hFile = CreateFile( argv[ 1 ], GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
if ( hFile == INVALID_HANDLE_VALUE )
{
    printf( "\nError: Unable to open file (%d)\n", GetLastError( ) );
    return -1;
}

it fails even if I replace argv[1] with hardcoded filename string. app is running as an admin.

encore leet
  • 55
  • 1
  • 1
  • 8
  • 1
    if you are running it from the IDE(I assume visual studio), afaik you have to place the file to the folder with the source code(the default folder) – Creris Aug 17 '15 at 17:17
  • Is the file in the executable's current, or "working", directory? If you're running from the command line, that's the same as your console's current directory; if you're using an IDE, there's a setting somewhere in it. – molbdnilo Aug 17 '15 at 17:18
  • Does [this](http://pastebin.com/i4kkQHf7) work for you? – ProXicT Aug 17 '15 at 17:28
  • 1
    I've found solution - if filename is `file.txt` CreateFile doesn't want `file.txt` input but `C:\\file.txt` – encore leet Aug 17 '15 at 17:34
  • 3
    Using relative paths is not ever not a mistake. – IInspectable Aug 17 '15 at 17:37
  • 2
    In [the](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx) documentation isn't said, the function requires absolute path. Did you try to check your current directory? – ProXicT Aug 17 '15 at 17:40
  • @ProXicT: When not using absolute paths, you are relying on external, uncontrollable state, namely the current working directory. The current working directory is shared among all threads in a process. A call to [SetCurrentDirectory](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365530.aspx) in one thread pwns another thread's assumptions. And even if you are using a single thread only, a call to [GetOpenFileName](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646927.aspx) can change the CWD behind your back. In short: Don't use relative paths. Ever. – IInspectable Aug 17 '15 at 18:08
  • @IInspectable that is poor advice in general. Sound advice for a GUI app. For a console app the working directory is by convention an input. – David Heffernan Aug 17 '15 at 18:53
  • @DavidHeffernan: Can a CLI process spawn multiple threads? Can a CLI process call `SetCurrentDirectory`? Is the current working directory a shared resource in a CLI process? Given the answers to those questions, how long will the convention survive? You even suggest in your answer to construct a fully qualified pathname. How is the advice poor then? – IInspectable Aug 17 '15 at 19:05
  • 1
    @IInspectable The asker wants to interpret the file name as relative to the executable dir and not the working dir. Hence the use of an absolute path. For a simple CLI program (think `ls` for instance) that starts, does its work in the main thread, and quits, then the convention is sound. – David Heffernan Aug 17 '15 at 19:09
  • @DavidHeffernan: If there were an `ls` application on Windows, it would call [GetCurrentDirectory](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364934.aspx) to retrieve a fully qualified pathname and take it from there. It still uses fully qualified pathnames for each and every call to the system. – IInspectable Aug 17 '15 at 19:16
  • @IInspectable I certainly use `ls` on Windows. Perhaps it does indeed use fully qualified path names as you say. I've never looked at its source. – David Heffernan Aug 17 '15 at 19:19
  • @DavidHeffernan: [How to create ls in windows command prompt?](http://stackoverflow.com/q/9362692/1889329). Whatever you are using, it's not part of Windows. The import table will tell you, whether the tool imports `GetCurrentDirectory` at least. – IInspectable Aug 17 '15 at 19:22
  • 3
    @IInspectable I think you know full well that I know my way around Windows. I know that `ls` is not part of Windows. It's a GNU tool that can perfectly well be run on Windows. I know I do. Whether or not this particular tool implements it that way, using relative paths for simple CLI programs is a valid choice. As it happens, the `ls` I'm looking at now does not import `GetCurrentDirectory`. FWIW. – David Heffernan Aug 17 '15 at 19:25
  • @IInspectable: **if** you are not in control of what your program's threads are doing, then you should certainly avoid using relative paths. For example, if your software allows third-party plugins. For simpler applications, insisting on using absolute paths in situations where relative paths would be more appropriate is an unnecessary complication. – Harry Johnston Aug 18 '15 at 00:32
  • @HarryJohnston: I already gave an example, where you (unknowingly) give up control ([GetOpenFileName](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646927.aspx)). This is not a third-party plugin. Other examples are in-proc COM servers. Again, this hardly qualifies as third-party plugins. In fact, you cannot be sure that even the least suspicious system call alters process-wide state. Don't rely on state, that is outside your control. If can prove me wrong, go ahead. If you cannot, take the advice. – IInspectable Aug 18 '15 at 00:38
  • @IInspectable: of course I can prove you wrong - the simple fact that software exists that uses relative paths and works perfectly well does that. Now of course if you use COM you're giving up control of your program's threads, so my proviso applies. (That's only one of the reasons I never use COM, or at least not from a command-line tool.) And David already pointed out that GUI applications are different. But if you're doing nothing but taking a file name from the command line and opening it, converting it to an absolute path first would be ridiculous. – Harry Johnston Aug 18 '15 at 00:52
  • @HarryJohnston: Uhm... Some software being lucky is your interpretation of a *proof*. Either you get serious, or I am out. – IInspectable Aug 18 '15 at 00:59
  • @IInspectable: knowing what you're doing does not constitute luck. If you can show me an API call that a realistic console application would want to use and which unexpectedly changes the current working directory, I'll concede. (*Unexpectedly,* mind you. The fact that a file selection dialog does so is hardly unexpected.) Otherwise I'm going to have to conclude that you're just being paranoid. Future readers can choose which advice they think best. – Harry Johnston Aug 18 '15 at 01:06
  • @IInspectable: at any rate, given that you seem to think the OP should be converting `argv[1]` into an absolute path before opening it, perhaps you should post an answer showing him how to do so? (Seriously; there *are* legitimate cases where this is necessary, e.g., if you want to interpret a file name relative to the executable file's location, which *is* what the OP seems to want in this case.) – Harry Johnston Aug 18 '15 at 01:10

2 Answers2

4

The error code is accurate. The file cannot be found. Possible explanations include:

  • You used the wrong file name.
  • You used a relative path and the process working directory is not what you expect it to be.

If you wish to interpret the file name as being relative to the directory in which the executable resides then do that. Form an absolute path from the directory containing the executable and the specified file name.

There is no reason to expect a process working directory to be the directory in which the executable resides.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
2

You are attempting to open a file using a relative pathname. Relative pathnames are relative to the current working directory (see GetCurrentDirectory). The current working directory is not necessarily the directory, where the executable image resides. It can be different for a number of reasons, for example:

  • The application was launched through a shortcut that explicitly sets the working directory.
  • The application called SetCurrentDirectory.
  • The application was launched through the command interpreter from a directory other than the executable's directory.

If you want to open a file located relative to the application's executable image, you need to construct a fully qualified pathname, based on the executable's location and the desired filename. The following code retrieves the executable's directory1):

#include <windows.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
#include <string>
#include <vector>

std::wstring GetExePath() {
    // Retrieve fully qualified module pathname
    std::vector<wchar_t> buffer( MAX_PATH );
    DWORD cbSize = ::GetModuleFileNameW( nullptr, buffer.data(),
                                         static_cast<DWORD>( buffer.size() ) );
    while ( cbSize == buffer.size() ) {
        buffer.resize( buffer.size() + MAX_PATH );
        cbSize = ::GetModuleFileNameW( nullptr, buffer.data(),
                                       static_cast<DWORD>( buffer.size() ) );
    }
    if ( cbSize == 0 ) {
        throw ::GetLastError();
    }

    // Remove filename from fully qualified pathname
    if ( ::PathRemoveFileSpecW( buffer.data() ) ) {
        ::PathAddBackslashW( buffer.data() );
    }

    // Construct string object from character buffer
    std::wstring str( &buffer[0] );
    return str;
}

This can be used as follows:

int wmain( int argc, const wchar_t* argv[] ) {

    if ( argc <= 1 ) {
        return -1;
    }

    std::wstring pathname = GetExePath();
    pathname += argv[1];
    HANDLE hFile = ::CreateFileW( pathname.c_str(), GENERIC_READ,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  NULL, OPEN_EXISTING, 0, NULL );
    if ( hFile == INVALID_HANDLE_VALUE )
    {
        wprintf( L"\nError: Unable to open file (%d)\n", GetLastError() );
        return -1;
    }

    // ...

    ::CloseHandle( hFile );
    return 0;
}


1) Code targeting Windows 8 and later should use PathCchRemoveFileSpec and PathCchAddBackslash instead.
IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    +1. Yet another possible cause: the application was launched using "run as admin" or was configured to require elevation. (The current directory is always the `system32` folder.) – Harry Johnston Aug 18 '15 at 21:37