Introduction
JesInclAnalyzer
is a simple application designed to filter out unwanted header files included in a C++ source file. I do not claim that this is the best solution for this particular problem. The basic idea is to compile each source file after commenting included header files in source, one by one, and to check the compiler output to determine if a particular header file is needed or not. Techniques used for the implementation are simple file operations and text processing. But it gives a fair result, I believe.
Background
As projects go through many phases, normally, its size increases. As new engineers come in and update source or add new features, they may add all header files which were used to add traditionally. In fact all those headers may not be needed. After a long time, build time for such modules will increase considerably and will tease developers by taking away major portion of productive time. I guess, even waiting 15 to 20 minutes to complete the build will not be a pleasant experience. The situation becomes worse when one is asked to fix a bug, urgently.
I was checking for solutions of this issue. I came across some commercial tools like Gimpel, Pro-Factor IncludeManager, etc. Another source of information was a paper submitted by AT&T Lab engineers; "Incl: A tool to analyze include files". This paper directly handles this problem; but for "C". However, the algorithm they proposed may be used for implementation. They have used a Program Database from AT&T Labs, called CIA (C Information Abstractor) which will provide sufficient information for "incl". I started analyzing if Program DataBase(PDB) generated by VC++ 6.0 compiler can provide same kind of information that "incl" needs. I needed an API set to get access to PDB files which I could achieve using DIA (Debug Interface Access) SDK that comes with Visual Studio 2005. I had to register "msdia80.dll" and modify some header files of this SDK so that I can use that in my development machine which is Windows 2000 (and VC++ 6.0 SP5). I could parse PDB and get information on all symbols and source files. But I could not find a way to get information about header files, global constants, etc. from PDB (PDB is generated by Post-compiler phase, I think). Also, pre-processor will expand all header files in its phase.
I could find another paper related to C++ Program Database from AT&T Labs. CIA++ is an enhancement to CIA; Program Database for C++. I analyzed that possibility also; i.e., to implement CIA++ algorithm and prepare input for "incl". But this also did not work for me due to lack of sufficient implementation details documentation or due to my lack of knowledge. Finally, I decided to go for a straighter method; that is to iterate through compilations after commenting each header file included in source files.
Using the Tool/Code
The tool (JesInclAnalyzer
) can be used as such. The tool requires that environment be set for "CL (VC++ 6.0 SP5 compiler)" to be able to execute from command line. Browse project folder containing source files and select command file for "CL". Command file should contain flags required for compilation and other parameters required by compiler. Click on "Analyze" button. The tool will analyze source files and will create copies of updated files with suffix "_Copy". Processing status will be updated in main dialog. Please note that the tool will not process "Read-only" files. It will give "Access denied" error message for "Read-only" files.
At first, the tool creates a list of source files available in given path with the help of JesCPPIterator
class. Method bool JesCPPIterator::PopulateFileList( const CString& csPath_i )
uses CFileFind
to create list of source files. Source files are kept in CStringArray
member and method bool JesCPPIterator::GetNextFile( CString& csSrcFile_o )
returns filenames one by one. I have no good reason to give you why I did not derive this class from CStringArray
instead of wrapping it up. What I did may not be a good design.
Class JesFileProcessor
creates copies of source files and is responsible for updating files; like commenting header file includes one by one, reverting changes, deleting unwanted copies, etc.
Class JesCompilerProxy
is responsible for communicating with compiler. Constructor of this class prepares pipe to be set as standard output and standard error of compiler. Method bool JesCompilerProxy::Compile()
creates compiler process after setting standard output and standard error handles with custom pipe handle. This code may be of some interest though it is not a complex or rare thing.
JesCompilerProxy::JesCompilerProxy( const CString& csCompiland_i )
: m_bObjCreated( true ),
m_bOPFileNeeded( false )
{
m_hReadPipe = 0;
m_hWritePipe = 0;
m_csCompiland = csCompiland_i;
HANDLE hTmpReadPipe;
SECURITY_ATTRIBUTES stSecAttribs;
stSecAttribs.nLength = sizeof( SECURITY_ATTRIBUTES );
stSecAttribs.bInheritHandle = TRUE;
stSecAttribs.lpSecurityDescriptor = 0;
if( !CreatePipe( &hTmpReadPipe, &m_hWritePipe, &stSecAttribs, BUFF_SIZE ))
{
AfxMessageBox( _T( "JesCompilerProxy creation failed..." ));
CloseHandles();
m_bObjCreated = false;
return;
}
if( !DuplicateHandle( GetCurrentProcess(), hTmpReadPipe,
GetCurrentProcess(), &m_hReadPipe,
0, FALSE, DUPLICATE_SAME_ACCESS ))
{
AfxMessageBox( _T( "JesCompilerProxy creation failed..." ));
CloseHandles();
m_bObjCreated = false;
return;
}
CloseHandle( hTmpReadPipe );
}
JesCompilerProxy::~JesCompilerProxy()
{
CloseHandles();
}
bool JesCompilerProxy::Compile()
{
if( !m_bObjCreated )
{
return false;
}
STARTUPINFO stStartupInfo;
::ZeroMemory( &stStartupInfo, sizeof( STARTUPINFO ));
stStartupInfo.cb = sizeof( STARTUPINFO );
stStartupInfo.dwFlags = STARTF_USESTDHANDLES;
stStartupInfo.hStdOutput = m_hWritePipe;
stStartupInfo.hStdError = m_hWritePipe;
PROCESS_INFORMATION stProcInfo;
::ZeroMemory( &stProcInfo, sizeof( PROCESS_INFORMATION ));
CString csCmdLine( "cl.exe /c " );
if( !m_csCLCmdFile.IsEmpty())
{
static const CString CMD_FILE_TAG = _T( "@" );
static const CString PARAMETER_DELIMITER = _T( " " );
csCmdLine += CMD_FILE_TAG + m_csCLCmdFile + PARAMETER_DELIMITER;
}
csCmdLine += m_csCompiland;
if( !CreateProcess( 0, csCmdLine.GetBuffer( csCmdLine.GetLength()),
0, 0, TRUE, CREATE_NO_WINDOW, 0, 0,
&stStartupInfo, &stProcInfo ))
{
AfxMessageBox( _T( "CreateProcess() failed..." ));
csCmdLine.ReleaseBuffer();
return false;
}
csCmdLine.ReleaseBuffer();
CString& csResult = ReadResultInPipe();
if( m_bOPFileNeeded )
{
DumpResult( csResult );
}
static const CString csErrMsg( _T( " error " ));
static const CString csWarnMsg( _T( " warning " ));
if( ( -1 != csResult.Find( csErrMsg, 0 ))) ( -1 != csResult.Find( csWarnMsg, 0 )))
{
return false;
}
return true;
}
Method CString JesCompilerProxy::ReadResultInPipe()
reads and returns output of compiler from pipe with the code shown below:
CString JesCompilerProxy::ReadResultInPipe()
{
CloseHandle( m_hWritePipe );
m_hWritePipe = 0;
CString csResult;
#ifdef _UNICODE
char szBuff[ BUFF_SIZE ] = { 0 };
#else
TCHAR szBuff[ BUFF_SIZE ] = { 0 };
#endif
DWORD dwReadBytesCnt = 0;
while( true )
{
if( !ReadFile( m_hReadPipe, szBuff, BUFF_SIZE, &dwReadBytesCnt, 0 )
|| ( 0 == dwReadBytesCnt ))
{
return csResult;
}
else
{
#ifdef _UNICODE
WCHAR wszResult[ BUFF_SIZE ] = { 0 };
if( MultiByteToWideChar( CP_ACP, 0, szBuff, strlen( szBuff ) + 1,
wszResult, sizeof( wszResult ) / sizeof( wszResult[ 0 ])))
{
csResult += wszResult;
continue;
}
else
{
continue;
}
#endif
csResult += szBuff;
}
}
}
Preparing CL (Compiler) Command Files from Project Settings
Preparing command files for compiler from Visual Studio 6.0 Project settings may not be that much difficult. Please check the steps given below:
- Copy "Project Settings"->"C/C++" tab->"Project Options:" to a text file.
- Remove flags/switches for Pre-Compilation/PDBs/OBJs/IDE/Optimization (E.g. :- /Fo"Debug/" /Fd"Debug/" /FD).
- Add include file paths needed for compilation with /I (Absolute paths).
- Please refer CL-Command line options in MSDN for details.
Points of Interest
Initially, Unicode build was not working properly. I found that ReadFile()
API does not fill WCHAR
buffer properly. I could not find any relevant documentation for this issue. Finally, I made a wild fix. Please check CString JesCompilerProxy::ReadResultInPipe()
code to meet that wild fox.
History
- Version 1.0 - 11-Mar-2009