Articles - Directory navigation

How do I get that File Name?!

This time we're going to look at a couple of APIs that Microsoft has supplied for retrieving file names and moving files within a directory.  I have seen a number of discussions about how to get a file out of a directory if the file-name isn't known at runtime.  For example, some process may have created a file who's name is comprised of a table name and the date.   Maybe something like this: F4211_102031.  So, in this case a process created this file from the Sales Order Detail (F4211) on January, 31 2002 (102031).  Now, because we don't actually know the file name when our second program runs we can't rely on hard coded names. 

Throw in an extra bit of complexity here: What if there are 5 files that we need to fetch out of that directory and they're all named differently? We're going to need a loop that sequentially returns each file-name to our program. 

Ok, now the last conundrum: we should do something with these files when we're done reading them.  You're getting good!  That's right, we have an API available that lets us move our files to another directory.

The beauty of all of this is that it's very simple to do.  I find it much easier to use than JDEs Business Functions, or jdeCallObject . . . .


  DSD5500260B    dsBSFN;
  ID             idReturnValue     = (ID)ER_SUCCESS;

   .... etc. . . 
  idReturnValue = jdeCallObject("BSFN", NULL,
                               (CALLMAP *)NULL,(int)0,(char *)NULL,
                               (char *)NULL,(int)0);

There's a lot of work involved in the call above to invoke JDEs buisiness function that gets file names from a directory.  Too much if you're going to run all of this on an NT box that can take advantage of the enormous API set that comes standard with the Windows dev tools, namely Visual C++.

So, lets get started

We want a simple technique to loop through all the file names in a directory and move those files as we go.  Here's our roadmap for the logic and APIs we're looking at:

Find the First File name
Loop while files are left to read
          Read in the found file
          "Do something with it"
          Move the read file to the Archive Directory
          Find the Next File name
Go back to the top of the loop



The neat thing here is that the pseudo code above is almost the whole program.  In fact, lets look at the real code now:


   HANDLE          hFindFile;
   BOOL            bFileFound        = FALSE,
   BOOL            bMoveResult       = FALSE;
   char            szDirectory[32],
   WIN32_FIND_DATA FindFileData;
   /* Pick the directory to look in */

  /* Find the First File Name */
  hFindFile = FindFirstFile(szFileToFind, &FindFileData);


  /* While more files exist, loop and return their names */
  while (bFileFound == TRUE) 
      /* Parse the data in the files. */
      //////////** THIS IS THE "DO SOMETHING WITH IT" PART **////////////
      /* Move the read file to the Archive Directory */
      bMoveResult= MoveFile(FindFileData.cFileName, // file name
                            szTargetFile);          // new file name and location

      /* Find the Next File Name */
      bFileFound= FindNextFile(hFindFile, &FindFileData);
  } //end of (bFileFound == TRUE)


A Closer Look:

Here's what the code is doing.  The first 6 lines should be clear, we're just declaring some variables.  After that we're using the directory that's passed into the program and appending "*.*" to it.  If you've used DOS you recognize this as a simple wild card search.  It's just saying 'look at all the files in this directory'.  Now, think of the implications of using wild cards.  If you only wanted the F4211 based file names (think back to our example above F4211_102031) then you could append "F4211*.*"!  That's offering a great amount of flexibility. You can pick and choose the files to read through if you know even a portion of their names.

Finally, the meat of the problem - FindFirstFile.  We're pretty much just handing it the location of the files and letting it tell us if anything is found.  Two things come back from this API, the first is hFindFile and the second is the FindFileData structure.  hFindFile is a 'handle' that can be tested to see if we located any files.  The structure is returned if a file is found.  Here's even more flexibility.  We not only get the file name itself, we get a host of other pieces of useful information.  Take a look at what's in the structure:

typedef struct _WIN32_FIND_DATA {
  DWORD dwFileAttributes; 
  FILETIME ftCreationTime; 
  FILETIME ftLastAccessTime; 
  FILETIME ftLastWriteTime; 
  DWORD    nFileSizeHigh; 
  DWORD    nFileSizeLow; 
  DWORD    dwReserved0; 
  DWORD    dwReserved1; 
  TCHAR    cFileName[ MAX_PATH ]; 
  TCHAR    cAlternateFileName[ 14 ]; 

Some of these things aren't terribly useful, but some could open some interesting possibilities like; 'file creation time', 'last access time', and 'last write time'.  Don't let the word TIME fool you, it includes the date too.  The 'filesizehigh/low' values can be used to calculate the exact size of the file.  I'm not sure how this could be used, but it's there for you imaginative types.  You can look up the rest of this structure in the MSDN Library.

Now that we have the name, and are certain that the file does exist (because of the hFindFile test) we can do something with it.  In the "Do Something With It" section you could use the old ANSI C standbys like 'fopen', 'fread', and 'fclose' to get into the file and read the data out of it.  Piece of pie.

Now we're done with the first file.  The MoveFile API gives us the ability to, you got it, move the file somewhere.  An archive directory somewhere would be a logical place to put processed files.  The MoveFile API takes only two arguments, the name of the file to move, and its new destination.  Here's something interesting though, you can change the name of the file by specifying a new name in the destination parameter.   So, you would specify something like: 'C:/[somepath]/newname.txt' in the second parameter.  The function itself returns a Boolean.  Zero is returned if the move failed.  The move will fail, for instance, if a file with the new name already exists in the destination directory specified.

The last step has been reached!  We've found the first file, processed it, and archived it.  Only one more thing to do, start over with the next file.   FindNextFile is the answer and it's just as easy to use as the other functions we've looked at.  It takes two parameters and returns a Boolean.  The first parameter is the value returned from FindFirstFile (hFindFile) and the second parameter is exactly the same as second parameter of FindFirstFile.  A zero is returned by the function if it fails.  The bottom of the loop is reached and we go on to process the file that was found.  If we've run out of files to process then the loop ends and we're done.

I won't cover it here, because I think we've tackled the real issues, but trapping errors from these three APIs is straight forward.  If any one of them fails call GetLastError and FormatMessage in sequence to receive a text description of what went wrong.  Of course there's a bit more typing than that, but take a look at the Microsoft documentation to see some example code.


JDEdwards certification definitely has demonstrated it's importance to a lot of people.   In my mind though, it's really not the end of the road when it comes to developing software for OneWorld.  There are just too many great tools available to rely solely on what JDE supplies.  Far from being a Microsoft cheer leader, I must admit that Microsoft has published a huge number of resources that are easy to use and fairly bug free.  This is just another example where stepping outside of 'the box' can reward the inquisitive software developer.  So, we've seen above that it's very simple to read through the list of files within a directory.  Also, it's only a small matter to take the additional step of moving those files to another location. 



example graphic