One of the problems you may find while developing a big project is the class
renaming.
For example I begin using an object of a class CConfig. Then a few moments
later I decide to change the class name from ‘CConfig’ to a more specific name
‘CInterfaceConfiguration’. It’s easy but the filenames remain unchanged. You can
change each file Config.h and Config.cpp. But if you have a really big project
the change becames a problem.
But I solve it partially. The code I made solves the problem parsing each
source file in the project, prompting for change or not the header, and the
corresponding source file (the correct name is builded from the definition
of the clases inside each header). I mean partially beacuse I suppose than a
header file named thing.h has a source named thing.cpp and than changes only
affect one project (the parsed one).
Using the program
It’s very simple to use. You must fill the line of project with the full path
of the *.dsp.
Then click on ‘do changes’ and wait the prompt of the program.
My recomendation is not change the header Sample.h by SampleApp.h where
Sample is ‘theApp’ because ClassWizard will include in new clases the header
‘Sample.h’ and not ‘SampleApp.h’.
Prompting…
When it finishes it shows some statistics and source files that would have
been changed.
Developing the program
The mechanism its easy in concept. You must open the .dsp locate the headers
and inside them locate the classes, make the correct changes and no more. As you
will imagine the solution is not so easy… Let’s take a look to the dsp file.
- # Microsoft Developer Studio Project File – Name="ODBC_Query" –
Package Owner=<4> - …
- # Begin Target
- # Name "ODBC_Query – Win32 Release"
- # Name "ODBC_Query – Win32 Debug"
- # Begin Group "Source Files"
- # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
- # Begin Source File
- SOURCE=.\ODBC_Query.cpp
- # End Source File
- # Begin Source File
- SOURCE=.\ODBC_Query.rc
- # End Source File
- …
- # Begin Source File
- SOURCE=.\StdAfx.cpp
- # ADD CPP /Yc"stdafx.h"
- # End Source File
- # End Group
- # Begin Group "Header Files"
- # PROP Default_Filter "h;hpp;hxx;hm;inl"
- # Begin Source File
- SOURCE=.\ODBC_Query.h
- # End Source File
… - # End Target
- # End Project
Is the group important? it depends on the developer that manages the project,
but it’s sure that a source file finishes in .cpp and a header file finishes in
.h, and both files are after a # Begin Source File declaration
in a line started with SOURCE=.
Then the code is like that:
// contains all files that not have class declarations CStringList SourceFiles; CStringList HeaderFiles; { // Now we need to scan de dsp file CString StringFile = _ReadStringFile(m_Project); { // Next step: Look for source files and header ones SourceFiles.RemoveAll(); // can be avoided (it's always empty) PD.SetRange(0,StringFile.GetLength()); PD.SetStatus("Looking for files in the project."); int ini_pos = 0; do { ini_pos=StringFile.Find("# Begin Source File",ini_pos); if (ini_pos==-1) break; PD.SetStep(ini_pos); // Ok, there is a source file CString FirstToken = "SOURCE=", LastToken = "\n"; // Get the file ini_pos = StringFile.Find(FirstToken,ini_pos) + FirstToken.GetLength(); int SFileSize = StringFile.Find(LastToken,ini_pos) - ini_pos-LastToken.GetLength(); CString SFile = StringFile.Mid(ini_pos,SFileSize); // It's valid? SFile.Replace('\\','/'); if (SFile.Left(2)=="./") SFile=SFile.Mid(2); if (!ExistFileAtWorkingDir(SFile)) { ::AfxMessageBox("The file "+SFile+" " "doesn't exist, avoiding it"); } else { // It's a header? CString Extension = SFile.Right(2); Extension.MakeUpper(); if(Extension==".H") { HeaderFiles.AddTail(SFile); m_Headers.AddString(SFile); }; Extension = SFile.Right(4); Extension.MakeUpper(); if(Extension==".CPP") { SourceFiles.AddTail(SFile); m_Sources.AddString(SFile); }; // other kind of files are not included } } while(true); } }
The next thing to examine is the definition of a class and it may be the most
smart.
If we suppose that a class is defined in one line (As it should be), we have
three patterns:
- class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1) :(1)
- class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1) {(1)
- class(1) export_d(0-1)…other_stuff(0-1) THECLASS(1)
Where the ‘(n)’ means the cardinality ( (0-1) means that it may exists or not
).
// Look for the first class definition bool ClassFound = false; while(!ClassFound) { CString Buffer; if (!fHead.ReadString(Buffer)) break; CString ClassToken = "class "; if ((Buffer.Left(ClassToken.GetLength())==ClassToken) &&(Buffer.Find(';')==-1)) // No pre-declarations here { // We found a class definition int Pos; // First pattern : // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1) :(1)' if ((Pos = Buffer.Find(':'))!=-1) // Ok { ClassName=Buffer.Left(Pos); ClassName.TrimRight(); Pos = ClassName.ReverseFind(' '); ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1)); ClassFound=true; } // Second pattern : // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1) {(1)' else if ((Pos = Buffer.Find('{'))!=-1) // Ok { ClassName=Buffer.Left(Pos); ClassName.TrimRight(); Pos = ClassName.ReverseFind(' '); ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1)); ClassFound=true; } else // Third pattern : // 'class(1) export_d(0-1) ... other_stuff(0-1) THECLASS(1)' { ClassName=Buffer; ClassName.TrimRight(); Pos = ClassName.ReverseFind(' '); ClassName=ClassName.Right(ClassName.GetLength()-(Pos+1)); ClassFound=true; } if(ClassFound) { int hItem=m_HeadersClasses.InsertItem(0,""); m_HeadersClasses.SetItemText(hItem,0,TheHeader); m_HeadersClasses.SetItemText(hItem,1,ClassName); CString CorrectHeader; if (ClassName.Left(1)=='C') CorrectHeader=ClassName.Mid(1); else CorrectHeader=ClassName; CorrectHeader+=".h"; if (TheHeader!=CorrectHeader) if (IDNO == ::AfxMessageBox(CString()+"Shall I change old:" +TheHeader+" by new:"+CorrectHeader,MB_YESNO)) { CorrectHeader=TheHeader; ClassFound=false; } if(ClassFound) { if(TheHeader!=CorrectHeader) { SourceDeps.SetAt(TheHeader,ClassName); ClassToCorrectHeader.SetAt(ClassName,CorrectHeader); m_HeadersClasses.SetItemText(hItem,2,"Need change"); } else { m_HeadersClasses.SetItemText(hItem,2,"No need to change"); } } } m_HeadersClasses.Arrange(LVA_DEFAULT); } } fHead.Close();
Performing changes
Well the next phase is the change, it will be done by changing the .dsp
project:
// Now the data is acquired, let's do the changes { // Change includes in dsp file // Open the file CString StringFile = _ReadStringFile(m_Project); for(POSITION Pos = SourceDeps.GetStartPosition(); Pos!=NULL;) { // Get the data CString TheHeader,ClassName,CorrectHeader; SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName); ClassToCorrectHeader.Lookup(ClassName,CorrectHeader); { // Header CString Pattern1 = "SOURCE="+TheHeader; CString Pattern2 = "/"+TheHeader; CString Pattern3 = "\\"+TheHeader; CString Pattern1Change = "SOURCE="+CorrectHeader; CString Pattern2Change = "/"+CorrectHeader; CString Pattern3Change = "\\"+CorrectHeader; StringFile.Replace(Pattern1,Pattern1Change); StringFile.Replace(Pattern2,Pattern2Change); StringFile.Replace(Pattern3,Pattern3Change); } { // Sources TheHeader = TheHeader.Left(TheHeader.GetLength()-1)+"cpp"; CorrectHeader = CorrectHeader.Left(CorrectHeader.GetLength()-1)+"cpp"; CString Pattern1 = "SOURCE="+TheHeader; CString Pattern2 = "/"+TheHeader; CString Pattern3 = "\\"+TheHeader; CString Pattern1Change = "SOURCE="+CorrectHeader; CString Pattern2Change = "/"+CorrectHeader; CString Pattern3Change = "\\"+CorrectHeader; StringFile.Replace(Pattern1,Pattern1Change); StringFile.Replace(Pattern2,Pattern2Change); StringFile.Replace(Pattern3,Pattern3Change); } } // Save the file _WriteStringFile(m_Project,StringFile); }
After change the project we must change the include references in source and header files.
{ // Perform changes in header and source files CStringList FilesToPerformReplace; FilesToPerformReplace.AddTail(&SourceFiles); FilesToPerformReplace.AddTail(&HeaderFiles); while(!FilesToPerformReplace.IsEmpty()) { // For each file CString TheFile = FilesToPerformReplace.RemoveHead(); // Open the file CString StringFile = _ReadStringFile(TheFile); // Perform replaces for(POSITION Pos = SourceDeps.GetStartPosition(); Pos!=NULL;) { // Get the data CString TheHeader,ClassName,CorrectHeader; SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName); ClassToCorrectHeader.Lookup(ClassName,CorrectHeader); CString Pattern1 = "\""+TheHeader+"\""; CString Pattern2 = "<"+TheHeader+">"; CString Pattern3 = "\\"+TheHeader+">"; CString Pattern1Change = "\""+CorrectHeader+"\""; CString Pattern2Change = "<"+CorrectHeader+">"; CString Pattern3Change = "\\"+CorrectHeader+">"; StringFile.Replace(Pattern1,Pattern1Change); StringFile.Replace(Pattern2,Pattern2Change); StringFile.Replace(Pattern3,Pattern3Change); } // Save the file _WriteStringFile(TheFile,StringFile); } // allfiles where replaced }
And rename the files….
{ // And now the last phase, renaming the files for(POSITION Pos = SourceDeps.GetStartPosition(); Pos!=NULL;) { // Get the data CString TheHeader,ClassName,CorrectHeader; SourceDeps.GetNextAssoc(Pos,TheHeader,ClassName); ClassToCorrectHeader.Lookup(ClassName,CorrectHeader); // The headers if (TheHeader!=CorrectHeader) { char *file, *filenew; file = _fullpath(NULL,TheHeader,0); filenew = _fullpath(NULL,CorrectHeader,0); CopyFile(file,filenew,false); DeleteFile(file); free(file); free(filenew); } // The sources TheHeader = TheHeader.Left(TheHeader.GetLength()-1)+"cpp"; CorrectHeader = CorrectHeader.Left(CorrectHeader.GetLength()-1)+"cpp"; if (TheHeader!=CorrectHeader) { char *file, *filenew; file = _fullpath(NULL,TheHeader,0); filenew = _fullpath(NULL,CorrectHeader,0); CopyFile(file,filenew,false); DeleteFile(file); free(file); free(filenew); } } }
And that’s all. Note that I use a file like a string, it’s a serializated
file. This is because it increases overall performance using Replace function of
CString class made in assembler.
// unique string-file functions CString CAdjustFileClassesDlg::_ReadStringFile(CString file) { CString StringFile; { // We load the file into a string char * str; CFile file(file,CFile::modeRead | CFile::shareDenyNone); StringFile.GetBufferSetLength(file.GetLength()+1); str = StringFile.LockBuffer(); file.ReadHuge(str,file.GetLength()); str[file.GetLength()]=0; file.Close(); StringFile.UnlockBuffer(); } return StringFile; } void CAdjustFileClassesDlg::_WriteStringFile(CString _file, CString data) { CFile file(_file, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone); file.WriteHuge(data.LockBuffer(),data.GetLength()-1); data.UnlockBuffer(); file.Close(); }
But for reading files, that have a relative path from the .dsp we first need
to do this:
// Get the base directory of the project m_Project.Replace('\\','/'); CString BaseDir; int pos = m_Project.ReverseFind('/'); if (pos==-1) { BaseDir=""; } else { BaseDir=m_Project.Left(pos+1); } if(-1==_chdir(BaseDir)) { ::AfxMessageBox("Something wrong in the path of " +m_Project+", exiting"); return; }
Well, I hope that this code will be interesting and useful for you like it
was for me.
Downloads
Download tools (includes debug build and source code) – 90 Kb