Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

The long Path to getting an proper cased Path & Filename with Win32


:P
On this page:

I'm working through a couple of small bugs in Html Help Builder today and one issue that's come up is that in some (but not all cases) file selections when inserting images and doing screen captures turn out in inaccurate casing. Images are embedded into the document typically as relative images and the although for HTML Help character casing doesn't matter, the inaccurate casing is a problem for some folks who are uploading their help files as HTML documents to a *nix Web server that's case sensitive.

So the question is how do you get a proper full file system filename from a given path using Win32?

Given filenames like this:

IMAGES\SOMEIMAGEFILE.GIF
C:\DOCUMENTS\HELPFILES\IMAGES\SOMEIMAGEFILE.GIF

I should end up with:

image\SomeImageFile.gif
c:\Documents\HelpFiles\Images\SomeImageFile.gif

Should be easy but it's taken me a few tries to get this right (I hope <g>).

Some time ago I went through a lot of the old codebase and fixed up all the path handling to ensure that links get embedded with true filenames. This problem originally arose because FoxPro tends to return all paths as Upper cased by default (dating back to the old xBase days) which wreaks havoc with keeping filenames properly cased for things like bulk copies and in this case for insertion of related links for resources like images etc.

So in a number of places I basically need to fix up upper cased filenames and convert them to their proper cased versions. Should be easy right? That's what I thought too. When I wrote the original code I created a generic function LongPath() that calls the GetLongPathName Win32 API function which appears at first glance to do the right thing although it's specifically meant to fix up short (8.3) paths to full long file names. But it also proper cases any passed in filename and it also works with relative pathnames:

DECLARE INTEGER GetLongPathName IN WIN32API  ;
  STRING lpFileName, ;
  STRING @lcBuffer, ;
  INTEGER lnBufferSize
  

And at first glance this appears to work. When I originally put this function in it worked just fine. But I - and several customers - noticed that mysteriously sometimes files were still getting injected with improper casing.

You see, LongPath() is specifically meant for 8.3 type paths. So as long as you use 8 character filenames and paths, it will in fact do the right thing. Since apparently I'm too old-school to use longer filenames all of my test code worked just fine. Duh.

I've looked around if there's some other API that gives you an adjusted path, but I couldn't really find anything that works well. GetFullPathName works in retrieving the full path, but it doesn't work leave relative paths alone nor does it return the file in proper case for whatever was passed in for the filename.

So a somewhat hacky way to get this to work is to convert the full path to a short path first, then call GetLongPathName with the adjusted path. Yeah, this is ugly as heck but it works both with true 8.3 short paths as well as with normal longer filenames. Maybe there's an easier way using high level Win32 APIs but for now this works.

The end result looks like this:

************************************************************************
FUNCTION ShortPath
******************
***  Function: Converts a Long Windows filename into a short
***            8.3 compliant path/filename
***      Pass: lcPath   -  Path to check
***    Return: lcShortFileName
*************************************************************************
LPARAMETER lcPath

DECLARE INTEGER GetShortPathName IN Win32API;
  STRING @lpszLongPath,	STRING @lpszShortPath,;
  INTEGER cchBuffer

lcPath = lcPath
lcshortname = SPACE(260)
lnlength = LEN(lcshortname)
lnresult = GetShortPathName(@lcPath, @lcshortname, lnlength)
IF lnResult = 0
   RETURN ""
ENDIF
RETURN LEFT(lcShortName,lnResult)

************************************************************************
FUNCTION LongPath
****************************************
***  Function: Returns the Long path name of a short path and properly
***            cases a path string based on directory entries.
***            Works off full or relative paths.
***      Pass: lcFilename - name of the file or path to case
***    Return: Properly cased path, or if file doesn't exist 
***            same string that was input.
************************************************************************
LPARAMETERS lcFileName
LOCAL lcFile, lcShortFileName, lnResult

DECLARE INTEGER GetLongPathName IN WIN32API  ;
  STRING lpFileName, ;
  STRING @lcBuffer, ;
  INTEGER lnBufferSize
  
lcFile=SPACE(255)

*** Must convert to ShortPath first in order for GetLongPathName to work
*** properly on filenames longer than 8 characters
lcShortFilename = ShortPath(lcFilename)
IF EMPTY(lcShortFileName)
  RETURN lcFileName
ENDIF  

lnResult = GetLongPathName(lcShortFileName,@lcFile,255)
IF lnResult = 0
   RETURN lcFileName
ENDIF
   
RETURN LEFT(lcFile,lnResult)
* LongPath()

This seems to work reliably and this has fixed the insertion issue throughout the application. LongPath() is used throughout to fix up paths for a number of file operations like insertion of resources as well as for the FTP file uploads to ensure the output sent to the server is sent with the same filenames as on the local system.

Yeah, I know this should have been done from day one, but this app is going on 10 years now and when first built there was no consideration for Web uploads and even less thought to *nix systems. Times change...

Posted in FoxPro  Windows  

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2019