Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Include File Hierarchy Viewer

, 29 Jul 2003 CPOL
A tool to view the include file hierarchy of your source code.
//====================================================================
// Although great care has gone into developing this software,
// it is provided without any guarantee of reliability, accuracy
// of information, or correctness of operation.  I am not responsible
// for any damages that may occur as a result of using this software.
// Use this software entirely at your own risk.
// Copyright 2003, Chris Richardson
//
// Description: Classes used to read VC7 projects and solutions.
//
//====================================================================

#include "stdafx.h"
#include "VC7Objects.h"
#include "PugXml.h"

#include <shlobj.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////
// Strings we care about in the workspace file.
//////////////////////////////////////////////////////////////////////

static const TCHAR s_pszProject[] = _T("Project(");
static const int s_iProjectLen = sizeof( s_pszProject ) - 1;

static const TCHAR s_pszNoInherit[] = _T("$(NOINHERIT)");
static const int s_iNoInheritLen = sizeof( s_pszNoInherit ) - 1;

//////////////////////////////////////////////////////////////////////
// Strings we care about in the project file.
//////////////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////////////
// CVC7Settings Implementation.
//////////////////////////////////////////////////////////////////////

CVC7Settings::CVC7Settings()
{
}
//
// ------------------------------------------------------------------
//
CVC7Settings::~CVC7Settings()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Settings::ReadFromXml( CPugXmlBranch * p_poBranch )
{
   // Get the preprocessor definitions, and include directories from the branch.
   CTCharString a_sDefines = p_poBranch->GetAttribute( _T("PreprocessorDefinitions") );
   CTCharString a_sIncludeDirs = p_poBranch->GetAttribute( _T("AdditionalIncludeDirectories") );
   CTCharString a_sIgnore = p_poBranch->GetAttribute( _T("IgnoreStandardIncludePath") );
   
   if( a_sIgnore.length() )
   {
      if( !_tcsicmp( a_sIgnore.c_str(), _T("TRUE") ) )
         c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
      else
         c_oSubtractSettings.push_back( CDSSetting( CDSSetting::SETTING_FLAG, _T("X") ) );
   }

   // Now parse them into the list.
   CStdStringArray a_oDefines;
   CStdStringArray a_oIncludes;
   ParseSeparatedList( a_sDefines.c_str(), _T(";,"), a_oDefines );
   ParseSeparatedList( a_sIncludeDirs.c_str(), _T(";,"), a_oIncludes );
   
   int i = 0;
   for( i = 0; i<a_oDefines.size(); ++i )
      c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_DEFINE, a_oDefines[i].c_str() ) );
   for( i = 0; i<a_oIncludes.size(); ++i )
      c_oAddSettings.push_back( CDSSetting( CDSSetting::SETTING_INCLUDE, a_oIncludes[i].c_str() ) );

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
void CVC7Settings::ParseSettings( CDSSettings * p_poFileSettings, CDSParsedSettings & p_roSettings ) const
{
   CVC7Settings * a_poFileSettings = static_cast<CVC7Settings*>(p_poFileSettings);

   CStdStringArray & a_roIncludes = p_roSettings.GetIncludes();
   CStdStringArray & a_roDefines = p_roSettings.GetDefines();
   
   CStdStringArray a_oStdIncludes;
   CDSWorkspace::GetStdIncludes( a_oStdIncludes );
   CStdStringArray a_oNewIncludes;
   CStdStringArray a_oNewDefines;
   
   bool a_bInheritIncludes = true;
   bool a_bInheritDefines = true;
   if( p_poFileSettings )
   {
      // Check if the file has NOINHERIT specified for it's include dirs or preprocessor definitions.
      CDSSettingArray::iterator a_oSubIter = a_poFileSettings->c_oAddSettings.begin();
      while( a_oSubIter != a_poFileSettings->c_oAddSettings.end() )
      {
         if( !_tcscmp( a_oSubIter->GetText(), s_pszNoInherit ) )
         {
            if( a_oSubIter->GetType() == CDSSetting::SETTING_INCLUDE )
               a_bInheritIncludes = false;
            else
            if( a_oSubIter->GetType() == CDSSetting::SETTING_DEFINE )
               a_bInheritDefines = false;
         }
         a_oSubIter++;
      }
   }

   // Parse our settings.
   CDSSettingArray::const_iterator a_oIter = c_oAddSettings.begin();
   while( a_oIter != c_oAddSettings.end() )
   {
      const CDSSetting & a_roSetting = *a_oIter;
      a_oIter++;

      // Check if the setting is in any of the file's "subtract" settings.
      if( a_poFileSettings )
      {
         if( a_poFileSettings->IsSubtractedSetting( a_roSetting ) )
            continue;
      }
      
      // Process the setting.
      const TCHAR * a_pszText = a_roSetting.GetText();
      switch( a_roSetting.GetType() )
      {
         case CDSSetting::SETTING_DEFINE:
            if( !a_bInheritDefines )
               continue;
            else
               a_oNewDefines.push_back( a_pszText );
            break;
         
         case CDSSetting::SETTING_INCLUDE:
            if( !a_bInheritIncludes )
               continue;
            else
               a_oNewIncludes.push_back( a_pszText );
            break;
         
         case CDSSetting::SETTING_FLAG:
            if( *a_pszText == _T('X') )
               a_oStdIncludes.clear();
            break;
      }
   }

   if( a_poFileSettings )
   {
      // Parse the file's settings.
      CDSSettingArray::iterator a_oIter = a_poFileSettings->c_oAddSettings.begin();
      while( a_oIter != a_poFileSettings->c_oAddSettings.end() )
      {
         const CDSSetting & a_roSetting = *a_oIter;
         a_oIter++;

         // Process the setting.
         const TCHAR * a_pszText = a_roSetting.GetText();
         switch( a_roSetting.GetType() )
         {
            case CDSSetting::SETTING_DEFINE:
               if( _tcscmp( a_pszText, s_pszNoInherit ) )
                  a_oNewDefines.push_back( a_pszText );
               break;
         
            case CDSSetting::SETTING_INCLUDE:
               if( _tcscmp( a_pszText, s_pszNoInherit ) )
                  a_oNewIncludes.push_back( a_pszText );
               break;
         
            case CDSSetting::SETTING_FLAG:
               if( *a_pszText == _T('X') )
                  a_oStdIncludes.clear();
               break;
         }
      }
   }

   // Add the new settings to the output settings.
   a_roIncludes.insert( a_roIncludes.begin(), a_oStdIncludes.begin(), a_oStdIncludes.end() );
   a_roIncludes.insert( a_roIncludes.end(), a_oNewIncludes.begin(), a_oNewIncludes.end() );
   a_roDefines.insert( a_roDefines.end(), a_oNewDefines.begin(), a_oNewDefines.end() );
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC7ConfigurationHolder Implementation.
//////////////////////////////////////////////////////////////////////

CVC7ConfigurationHolder::CVC7ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
CVC7ConfigurationHolder::~CVC7ConfigurationHolder()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7ConfigurationHolder::ReadFromXml( CPugXmlBranch * p_poBranch,
                                                  const TCHAR *   p_pszChildName )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;

   // Find all children with the specified name.
   CPugXmlBranchArray a_oConfigs;
   p_poBranch->FindAllElements( p_pszChildName, a_oConfigs );
   
   // Parse the configuration settings from each child node.
   CVC7Settings * a_poSettings = NULL;
   for( int i = 0; i<a_oConfigs.GetCount(); ++i )
   {
      CPugXmlBranch a_oConfiguration = a_oConfigs.GetAt( i );
      
      CPugXmlBranch a_oCompilerTool = a_oConfiguration.FindFirstElemAttr( _T("Tool"), _T("Name"), _T("VCCLCompilerTool") );
      if( a_oCompilerTool.IsNull() )
         continue;
      
      a_poSettings = new CVC7Settings;
      c_oSettings.insert( CDSMapConfigToSettings::value_type( a_oConfiguration.GetAttribute( _T("Name") ), a_poSettings ) );

      a_eStatus = a_poSettings->ReadFromXml( &a_oCompilerTool );
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC7File Implementation.
//////////////////////////////////////////////////////////////////////

CVC7File::CVC7File()
{
   c_poConfigHolder = new CVC7ConfigurationHolder;
}
//
// ------------------------------------------------------------------
//
CVC7File::~CVC7File()
{
   if( c_poConfigHolder )
   {
      delete c_poConfigHolder;
      c_poConfigHolder = NULL;
   }
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7File::ReadFromXml( const TCHAR *    p_pszProjectDir,
                                   CPugXmlBranch *  p_poBranch )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;
   
   // First try to find the "FullPath" branch. 
   CTCharString a_sPath = p_poBranch->GetAttribute( _T("FullPath") );
   if( !a_sPath.length() )
   {
      // Hopefully it will have a relative path.
      a_sPath = p_poBranch->GetAttribute( _T("RelativePath") );
      if( !a_sPath.length() )
         return PARSE_STATUS_GENERIC_ERROR;

      // Get the full path of the project from the relative path.
      c_sPath = GetFullPath( p_pszProjectDir, a_sPath.c_str() );
   }
   else
      c_sPath = a_sPath;

   DetermineName();
   
   // Read any per file configuration settings we may have.
   a_eStatus = static_cast<CVC7ConfigurationHolder*>(c_poConfigHolder)->ReadFromXml( p_poBranch, _T("FileConfiguration") );
   
   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC7Project Implementation.
//////////////////////////////////////////////////////////////////////

CVC7Project::CVC7Project()
{
}
//
// ------------------------------------------------------------------
//
CVC7Project::~CVC7Project()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Project::ReadProject( const TCHAR * p_pszPath )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;

   // Get the directory and drive information out of the project path.
   TCHAR a_szDriveAndDir[MAX_PATH + 1] = {0};
   TCHAR a_szDirectory[MAX_PATH + 1] = {0};
   _tsplitpath( p_pszPath, a_szDriveAndDir, a_szDirectory, NULL, NULL );
   _tcscat( a_szDriveAndDir, a_szDirectory );
   
   // Parse the project's XML.
   CPugXmlParser a_oParser;
   if( !a_oParser.ParseFile( p_pszPath ) )
      return PARSE_STATUS_ERRORS_OCCURRED;
   
   // Get the configurations branch, so we can iterate through them.
   CPugXmlBranch a_oConfigurations = a_oParser.GetRoot().FindFirstElement( _T("Configurations") );
   if( !a_oConfigurations.HasChildren() )
      return PARSE_STATUS_OK;
   
   // Read the project configurations.
   CVC7ConfigurationHolder a_oConfigurationHolder;
   a_eStatus = a_oConfigurationHolder.ReadFromXml( &a_oConfigurations, _T("Configuration") );
   if( a_eStatus != PARSE_STATUS_OK )
      return a_eStatus;
   
   // Create a configuration object for each configuration we read.
   CDSMapConfigToSettings & a_roSettings = a_oConfigurationHolder.GetSettings();
   CDSMapConfigToSettings::iterator a_oIter = a_roSettings.begin();
   while( a_oIter != a_roSettings.end() )
   {
      CDSConfiguration * a_poConfig = new CDSConfiguration( a_oIter->first.c_str(), a_oIter->second );
      c_oConfigurations.push_back( a_poConfig );
      
      // Remove the settings from the config holder.
      a_roSettings.erase( a_oIter );
      a_oIter = a_roSettings.begin();
   }
   
   // Get the Files branch, so we can iterate through them.
   CPugXmlBranch a_oFiles = a_oParser.GetRoot().FindFirstElement( _T("Files") );
   if( !a_oFiles.HasChildren() )
      return PARSE_STATUS_OK;
   
   // Get an array of all the files under the Files branch.
   CPugXmlBranchArray a_oFileList;
   a_oFiles.FindAllElements( _T("File"), a_oFileList );
   for( int i = 0; i<a_oFileList.GetCount(); ++i )
   {
      CPugXmlBranch a_oFile = a_oFileList.GetAt( i );
      
      // Create a new file, and read it's settings.
      CVC7File * a_poFile = new CVC7File;
      a_eStatus = a_poFile->ReadFromXml( a_szDriveAndDir, &a_oFile );

      c_oFiles.push_back( a_poFile );
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//


//////////////////////////////////////////////////////////////////////
// CVC7Workspace Implementation.
//////////////////////////////////////////////////////////////////////

bool CVC7Workspace::s_bHaveStandardIncludes = false;

// I don't have the latest platform SDK installed with VC6, so I took this from the VC7 shlobj.h header.
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA             0x001c        // <user name>\Local Settings\Applicaiton Data (non roaming)
#endif // CSIDL_LOCAL_APPDATA

CVC7Workspace::CVC7Workspace()
{
   if( !s_bHaveStandardIncludes )
   {
      // Get the directory VC7 is installed in, so we can replace "($VCInstallDir)" macros with the correct path.
      TCHAR a_szVCDir[MAX_PATH + 1] = {0};
      CRegKey a_oVCDirKey;
      if( GetRegKey( _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.0"), a_oVCDirKey ) )
      {
         unsigned long a_ulCount = MAX_PATH;
         if( a_oVCDirKey.QueryValue( a_szVCDir, _T("InstallDir"), &a_ulCount ) == ERROR_SUCCESS )
         {
            TCHAR * a_pszSep = _tcsstr( a_szVCDir, _T("Common7") );
            if( a_pszSep )
               *a_pszSep = 0;
            _tcscat( a_szVCDir, _T("vc7\\") );
         }
      }

      enum { MAX_INCLUDE_DIRS = MAX_PATH * 20 };
      TCHAR a_szIncludeDirs[MAX_INCLUDE_DIRS] = {0};
      unsigned long a_ulCount = MAX_INCLUDE_DIRS;
      
      // Apparently this function isn't present on some NT boxes :(, so we'll do it the hard way with ID lists...
      //if( SUCCEEDED( SHGetSpecialFolderPath( NULL, a_szVCDatPath, CSIDL_LOCAL_APPDATA, FALSE ) ) )

      // Attempt to read the include directories from the user's AppData directory.  If it doesn't exist, we'll read it from the registry.
      bool      a_bFoundVCIncludeVars         = false;
      TCHAR     a_szVCDatPath[MAX_PATH + 1]   = {0};
      IMalloc * a_poMalloc                    = NULL;
      if( SUCCEEDED( SHGetMalloc( &a_poMalloc ) ) )
      {
         LPITEMIDLIST a_pstPathIDList = NULL;
         if( SUCCEEDED( SHGetSpecialFolderLocation( NULL, CSIDL_LOCAL_APPDATA, &a_pstPathIDList ) ) )
         {
            if( SHGetPathFromIDList( a_pstPathIDList, a_szVCDatPath ) )
            {
               _tcscat( a_szVCDatPath, _T("\\Microsoft\\VisualStudio\\7.0\\VCComponents.dat") );
         
               FILE * a_pstFile = _tfopen( a_szVCDatPath, _T("rt") );
               if( a_pstFile )
               {
                  while( _fgetts( a_szIncludeDirs, MAX_INCLUDE_DIRS, a_pstFile ) )
                  {
                     const int s_iIncludeDirsLen = _tcslen( _T("Include Dirs=") );
                     if( !_tcsncmp( a_szIncludeDirs, _T("Include Dirs="), s_iIncludeDirsLen ) )
                     {
                        // We found it.
                        a_bFoundVCIncludeVars = true;
                  
                        int a_iBytes = sizeof( TCHAR ) * (_tcslen( a_szIncludeDirs ) - s_iIncludeDirsLen);
                        memmove( a_szIncludeDirs, a_szIncludeDirs + s_iIncludeDirsLen, a_iBytes );
                  
                        break;
                     }
                  }

                  if( !a_bFoundVCIncludeVars )
                     a_szIncludeDirs[0] = _T('\0');
            
                  fclose( a_pstFile );
               }
            }

            a_poMalloc->Free( a_pstPathIDList );
            a_pstPathIDList = NULL;
         }
         
         a_poMalloc->Release();
         a_poMalloc = NULL;
      }

      if( !a_bFoundVCIncludeVars )
      {
         // Read the standard include directories from the registry.
         CRegKey a_oKey;
         if( GetRegKey( _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.0\\VC\\VC_OBJECTS_PLATFORM_INFO\\Win32\\Directories"), a_oKey ) )
         {
            if( a_oKey.QueryValue( a_szIncludeDirs, _T("Include Dirs"), &a_ulCount ) == ERROR_SUCCESS )
               a_bFoundVCIncludeVars = true;
         }
      }

      if( a_bFoundVCIncludeVars )
      {
         // We found the VCDir, and the Directories key, so read the directory list and parse it.
         ParseSeparatedList( a_szIncludeDirs, _T(";"), s_oStdIncludes );
         
         // Perform the macro replacement.
         for( int i = 0; i<s_oStdIncludes.size(); ++i )
         {
            CTCharString a_sDir = s_oStdIncludes[i];

            TCHAR a_szCurrentDir[MAX_PATH + 1] = {0};
            TCHAR * a_pszMacro = _tcsstr( a_sDir.c_str(), _T("$(VCInstallDir)") );
            if( a_pszMacro )
            {
               a_pszMacro += _tcslen( _T("$(VCInstallDir)") );
               
               CTCharString a_sNewDir = a_szVCDir;
               a_sNewDir.append( a_pszMacro );
               s_oStdIncludes[i] = a_sNewDir;
            }
         }
      }

      // Don't need to read them again.
      s_bHaveStandardIncludes = true;
   }
}
//
// ------------------------------------------------------------------
//
CVC7Workspace::~CVC7Workspace()
{
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Workspace::ParseLine( TCHAR * p_pszLine )
{
   ParseStatus a_eStatus = PARSE_STATUS_OK;
   if( !_tcsncmp( p_pszLine, s_pszProject, s_iProjectLen ) )
   {
      TCHAR * a_pszSep = _tcschr( p_pszLine, _T('=') );
      if( !a_pszSep )
         return PARSE_STATUS_OK;

      // Extract the project name.
      TCHAR a_szProjectName[MAX_PATH + 1] = {0};
      a_pszSep = ExtractString( a_szProjectName, a_pszSep, _T('\"') );
      
      // Extract the relative path to the project.
      TCHAR a_szRelativeName[MAX_PATH + 1] = {0};
      ExtractString( a_szRelativeName, a_pszSep, _T('\"') );

      // Get the full path of the project from the relative path.
      CTCharString a_sProjectPath = GetFullPath( c_sDirectory.c_str(), a_szRelativeName );

      a_eStatus = ReadOneProject( a_sProjectPath.c_str() );
   }

   return PARSE_STATUS_OK;
}
//
// ------------------------------------------------------------------
//
ParseStatus CVC7Workspace::ReadOneProject( const TCHAR * p_pszProjectPath )
{
   // Create a project and parse it's contents.
   CVC7Project * a_poProject = new CVC7Project;
   ParseStatus a_eStatus = a_poProject->ReadProject( p_pszProjectPath );
   if( a_eStatus != PARSE_STATUS_OK )
      delete a_poProject;
   else
      c_oProjects.push_back( a_poProject );
   
   return a_eStatus;
}
//
// ------------------------------------------------------------------
//

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Chris Richardson
Software Developer (Senior)
United States United States
I like to program, I like to sail.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 30 Jul 2003
Article Copyright 2003 by Chris Richardson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid