Storing Relative File Names

Environment: MS Visual C++ 6

Many MFC documents use CArchive to store the filenames of nested documents or project files. When the user copies the projects into different directories or to a different computer, the paths become invalid unless the programmer painstakingly controls these directory name dependencies. It also frustrates the user quite a lot if the application does not support such project movements. This article describes an easy way to solve this problem.

We will suppose that we are creating an MFC Document/View application and our archived CDocument contains the filenames of project-related files. For instance, the document contains a reference to the measured file somewhere and the settings file somewhere else. Typically, the user holds the measured data and settings data in the same root directory. Frequently, the settings are stored in some subfolder. So, we will obtain something like this (mdc means our measurement document extension):


Yes, all three mdc files refers to the same setup.dat internally, as you can imagine. Also, what do we do if we can expect that the user will move the project onto a file server? We can handle filenames as relative, but the application will contain a lot of code for name splitting and concatenating. Also, direct references (for instance, if setup.dat is in a completely separate directory, such as "c:\setups\") must be painstakingly controlled.

The following code tries to solve the problem in the root—in archive storing and reading. At that moment, we know the archive path and we can compare which file names are in the archive directory and its subfolders. By feeding _getDocRelativeFileName() with the full path of document archive and the full path of the stored file name, the method can decide whether the file name is relative to the archive. Also, from
we will get
and from
we will get

When reading back the archive, _getDocAbsoluteFileName() will restore the full file names. Also, when the user moves the project into
we will read back the correct, fully qualified file names

The code fragment presented is dirty and could be better, of course, but it works in many situations. Comments are included in the code fragment. Some additional safety checks can be implemented, of course. Currently, the code:

  • Converts only files in the project and nested directories to the path-relative file names
  • Works both for drive locations and server locations ("c:\something\" vs. "\\servername\something")
  • Does not serve directory-up file placement (like "..\settings\setup.dat")
  • Was not tested on a unicode build

You could need more sophisticated file position support; then, it is necessary either to enhance the presented functionality or to create other, similar function pairs. However, placement in the Serialize method is easy and understandable and it frees the rest of the application from lot of stupid work.

To use the code, simply copy it into the application document code (somewhere in xxxdoc.cpp, before the CxxxDoc::Serialize method) or make them methods of the CxxxDoc class. Add function calls to the Serialize method, as shown in the example. No more work is needed; the rest of application from this moment does not depend on current project placement.

// static functions implementation to operate with project-
// relative file names
// an include might be needed if not specified already somewhere

#include <stdlib.h>

// ===============================================================
// compare drive and path of the document and the filename;
// returns only the different part (eg. filename with extension if
// the file is in the same folder as the document file, or subpath
// and filename if file is in nested directory). Use
// _getDocAbsoluteFileName to revert to the full path.
// sDocName is the full file name of the archived CDocument;
// sSubFileName is the full file name of (possibly) project-nested
// data file.

static CString _getDocRelativeFileName( CString& sDocName,
                                        CString& sSubFileName )
    CString sResult = sSubFileName;

    char ddrive[_MAX_DRIVE];
    char ddir[_MAX_DIR];
    char dfname[_MAX_FNAME];
    char dext[_MAX_EXT];
    char fdrive[_MAX_DRIVE];
    char fdir[_MAX_DIR];
    char ffname[_MAX_FNAME];
    char fext[_MAX_EXT];

    _splitpath( sDocName, ddrive, ddir, dfname, dext );
    _splitpath( sSubFileName, fdrive, fdir, ffname, fext );

    if ( strlen(ddrive) > 0 || strlen(ddir) > 0 )
        CString s = fdir;
        if ( CString( ddrive ) == CString( fdrive ) &&
            s.Find( ddir ) == 0 )
            sResult = s.Mid( strlen( ddir ));
            sResult += ffname;
            sResult += fext;

    return sResult;

// Changes the relative file name from _getDocRelativeFileName
// to the absolute path. Checks for server name
// \\xxx and drive name x:\ to be sure to not expand fully
// qualified names. sDocName is the full file name of the restored
// CDocument. sSubFileName is the optionally shortened file name
// of the project-nested data file.

static CString _getDocAbsoluteFileName( CString& sDocName,
                                        CString& sSubFileName )
    CString sResult = sSubFileName;

    char ddrive[_MAX_DRIVE];
    char ddir[_MAX_DIR];
    char dfname[_MAX_FNAME];
    char dext[_MAX_EXT];

    _splitpath( sDocName, ddrive, ddir, dfname, dext );

    // there should not be a server of the disk identifier in
    // the relative name
    // (a little bit of safe expansion was used)
    if ( sSubFileName.Find( "\\\\" ) < 0 &&
         sSubFileName.Find( ":\\" ) < 0 )
        sResult = CString(ddrive) + CString(ddir) + sSubFileName;

    return sResult;

// example of the function use

void CMyAppDoc::Serialize(CArchive& ar)
    CString sPath;
    CFile* pFile = ar.GetFile();
    if ( pFile )
        sPath = pFile->GetFilePath();
    if (ar.IsStoring())
        // TODO: add storing code here
        ar << _getDocRelativeFileName( sPath, m_sFileName1 );
        ar << _getDocRelativeFileName( sPath, m_sFileName2 );
        // TODO: add loading code here
        CString s;
        ar >> s;
        m_sFileName1 = _getDocAbsoluteFileName( sPath, s );
        ar >> s;
        m_sFileName2 = _getDocAbsoluteFileName( sPath, s );

// ===============================================================


Please copy the code directly from the article body....


  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date