troubles with FindFirstFile()

Sandra-24
Sandra-24 used Ask the Experts™
on
And Murphy's law of programming strikes again. An old section of code that was working up untill today has ceased functioning without being changed and for no apparent reason.

I posted the code below. FindFirstFile() is returning and INVALID_HANDLE_VALUE and GetLastError() returns 3 (how do I find out what that means???). The path and pattern given is perfectly valid, and what's more has worked in the past and hasn't been changed since. I'm not certain what's gone wrong or how to address the problem. I've spent the last 3 hours trying to though :-(. Does anybody have any wisdom to share?

vector<string> Directory::GetFiles(string path, const char * pattern)
{
      vector<string> files;
      WIN32_FIND_DATA fd;
      HANDLE handle = FindFirstFile((path + pattern).c_str(), &fd);
      if(handle == INVALID_HANDLE_VALUE)
      {
            files.resize(0); //set the size to 0 so we can't iterate through the vector
            return files;
      }

      do
      {
            files.push_back(path + fd.cFileName);
      } while(FindNextFile(handle, &fd));
      
      FindClose(handle);

      return files;
}
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
jkr
Top Expert 2012
Commented:
>>GetLastError() returns 3 (how do I find out what that means???).

"winerror.h" is a great place to look for error codes :o)

//
// MessageId: ERROR_PATH_NOT_FOUND
//
// MessageText:
//
//  The system cannot find the path specified.
//
#define ERROR_PATH_NOT_FOUND             3L

What are "path" and "pattern" when the function is called? Maybe there is just a missing backslash betweehn the path and the pattern...
Top Expert 2005

Commented:
Print out (even in a debugger) the values of
path.c_str()
pattern
(path + pattern).c_str()

before calling FindFirstFile.  I've found that if some code "just stops working
even though it hasn't changed", then some code that has changed has
mangled something.  Check that the strings are not corrupt and contain
the expected values.

Oh, and make sure you have read permissions for the directory in question.

Author

Commented:
Hmmm...

printf((path + pattern).c_str()) spits out: emails\0\*.eml

which is a valid path to a directory containing .eml files

It is interesting to note that right-clicking > properties on the the emails and 0 directory shows that they are Read-Only. Changing this setting off of Read-Only causes something to reset it to Read-Only. Actually it appears all directories are Read-Only as a fact of being directories. The *.eml files are not Read-Only or Hidden, or have any special permissions.

What could it be?
OWASP: Forgery and Phishing

Learn the techniques to avoid forgery and phishing attacks and the types of attacks an application or network may face.

Top Expert 2012
Commented:
>>which is a valid path to a directory containing .eml files

Is that an absolute or relative path? Cpuld that be the problem? What is the working ( "current") directory then?

Author

Commented:
Again jkr nails it. It was a relative path of course, and I (very skeptically) switched it for an absolute path and it worked.
I have no idea why, and even less of an idea why it used to work with a relative path, but doesn't anymore! How much did it take before you knew this much about programming jkr? Thanks for the help!

-Sandra
jkr
Top Expert 2012

Commented:
>>How much did it take before you knew this much about programming jkr?

I still do not know enough except that once you stop learning, chances are that you are dead :o)

Commented:
Sandra-24,
Did you check to see if one of your path strings don't have a '\' by itself, instead of a pair '\\'

Example:
path = "c:\\tmp\data"; //This will fail because the character after tmp is not a '\\'

Commented:
FYI:

Your code would be more efficient if you passed the path string by reference, and passed a reference to the vector<string>.
Example:
void Directory::GetFiles(const string &path, const char * pattern, vector<string> &files)

It is very inefficient to pass back a vector<string> by value.  You end up calling two extra constructors and two extra dtor.
With those two extra constructors you have the added extra constructor call for each string in the vector.

It's also better to pass in the pattern by constant string reference.
void Directory::GetFiles(const string &path, const string &pattern, vector<string> &files)

This is better, because you can use it with both string type and const char* type with out having to use the string::c_str() function.
So with the above type, you can do the following:
      std::string path1="test";
      std::string pattern1 = "test";
      
      const char* path2="test";
      const char* pattern2 = "test";

      GetFiles(path1, pattern1, container1);
      GetFiles(path2, pattern2, container1);
      GetFiles("test", "test", container1);

Now if you really want to get fansy, you can make your function even more generic by making it a template member function.

template<class T>
void Directory::GetFiles(const string &path, const string &pattern, T &files)
{
    WIN32_FIND_DATA fd;
    HANDLE handle = FindFirstFile((path + pattern).c_str(), &fd);
    if(handle == INVALID_HANDLE_VALUE)
    {
         files.resize(0); //set the size to 0 so we can't iterate through the vector
         return;
    }

    do
    {
         files.push_back(path + fd.cFileName);
    } while(FindNextFile(handle, &fd));
   
     FindClose(handle);
}

This allows you to use any type container with your function.  However, the down side is that you have to put this function in the header.

Commented:
One last point.

You should consider adding a check for '\\' being the last character in the path string.
Of couse if you do this, then you're better off going back to your original version, and pass path by value.

Example:
template<class T>
void GetFiles(string path, const string &pattern, T &files)
{
    WIN32_FIND_DATA fd;
      if (path.size() && path[path.size()-1] != '\\') path+="\\";
    HANDLE handle = FindFirstFile((path + pattern).c_str(), &fd);
    if(handle == INVALID_HANDLE_VALUE)
    {
         files.resize(0); //set the size to 0 so we can't iterate through the vector
         return;
    }
    do
    {
         files.push_back(path + fd.cFileName);
    } while(FindNextFile(handle, &fd));
     FindClose(handle);
}

int main(int argc, char* argv[])
{
      vector<string> container1;
      deque<string> container2;
      list<string> container3;

      std::string path1="C:\\";
      std::string pattern1 = "*.txt";
      
      const char* path2="C:\\";
      const char* pattern2 = "*.txt";

      GetFiles(path1, pattern1, container1);
      GetFiles(path2, pattern2, container2);
      GetFiles("C:", "*.txt", container3);

      cout << container1.size() << endl;
      cout << container2.size() << endl;
      cout << container3.size() << endl;
      
      system("pause");
      return 0;
}


Sorry for the long winded OT comments, but I'm very board at work, and have no one else to play with. :-)

Author

Commented:
Thanks Axter, sometimes I forget that strings are objects and not a built in data type (and thus might be expensive to copy constantly).

I didn't realize deque or list existed untill now. I wonder how much else there is that I didn't know existed? maybe an easier question is what do I know about :-)

Thanks for your comments & improvements to my function. I created the Directory And File static classes because I missed the Directory::Create() File::Exists() functionailty of .NET

I could use a namespace and functions rather but this leaves open the possibility of extending the classes into instantiable objects at a later date.

-Sandra

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial