Click here to Skip to main content
Email Password   helpLost your password?

This project's ClassView structure

Introduction

I was reading Jignesh Patel's article here on CP and I found someone asking for a text version of the ClassView information. Actually, I needed that too for a project where I wanted to rename some classes and source files, so I took the challenge and tried to decode ClassView binary data.

This is for you, Uwe! ;-)

Binary formats and Hex editors

Well, since the ".opt" file is a Compound Document (or Structured Storage) file I opened it with the DocFile Viewer utility that comes with Visual Studio and I used the internal Hex viewer to try to decode the "ClassView Window" stream.

The first thing I noted was the length of the stream reported on the window's title bar with respect to what seemed the actual data: some strings of text and a few other bytes of header. The rest of the stream is just garbage, so it's sufficient to save the stream length and fill it up with zeroes while loading.

The next thing to do when you find strings in a block of binary data is to see if they're zero terminated, like C strings, or prefixed with their length, like Pascal or Basic strings, or maybe fixed length. I found all strings were prefixed with a BYTE or a WORD, matching the string's length.

The other fields all seemed like WORD or DWORD values or flags. Some could be identified as the project's count in the workspace, or the folder's count in a project, or the number of subfolders and classes in a folder.

A strange thing was the presence of two unknown class names before the first project in the workspace and before the first folder of the first project: CClsFldSlob and CClassSubfolderSlob. I suspected this classes were owned by the Visual Studio IDE, so I used Nick Hodapp's OpenVC add-in and looked for them:

CClsFldSlob
CClassSubfolderSlob

Once found, I started thinking if I could use this information to my purposes, but my research about those two classes ended there (see the Addendum). Maybe their presence among the other data is due to the process used to serialize data into and out of the stream, maybe it's just the MFC's serialization support, but I can't tell it because I never used that.

No problem, this extraneous data can be easily identified by a WORD prefix of 0xFFFF. Then comes another WORD of value 0x0001 or 0x0002 (that I called "slob level", but it actually is the object schema of MFC), the class name and then the first project or folder of the whole workspace. The other projects have a WORD prefix of 0x8001, while folders have a WORD prefix of 0x8003 (both are special index values, see the Addendum).

All these could be flags or integer numbers. I chose not to explicitly write to XML the first WORD prefix, which is implicit in the type of item considered (project, folder or "slob" - dropped in last version, see the Addendum).

For each project there's a DWORD count of the project's folders, while for each folders there are two DWORDs counting the number of subfolders and contained classes. The tree is serialized with an anticipated visit, that is subfolders are nested just after the subfolders count.

All the details can be found in the CCLVParser class, but don't expect a documentation for the ClassView data format, just commented source code.

Addendum: MFC Serialization

I was trying to investigate the problems reported by some users of this add-in and decided to have a look at the way MFC handles object serialization. I was right, the binary data in the "ClassView Window" stream is compatible with the serialization support of MFC, that almost certainly was used to read and write the stream.

Let's skip the first DWORD of the stream, that I consider like a signature because it never changes, but that may have a deeper meaning. The next field is a DWORD count of the projects in the workspace, followed by a representation of each project. This is very close to how MFC collections are stored during serialization. Take for example the Serialize method of CObArray or CObList, that could be conveniently used to store the list of projects in a workspace:

void CObArray::Serialize(CArchive& ar)
{
    ASSERT_VALID(this);
    CObject::Serialize(ar);
    if (ar.IsStoring())
    {
        ar.WriteCount(m_nSize);
        for (int i = 0; i < m_nSize; i++)
            ar << m_pData[i];
    }
    else
    {
        ...
    }
}

Obviously this code cannot be the source of our stream, because the object itself is first serialized and there's no trace of MFC collection classes in the data stream before the initial count, but I hope you got the picture. First the object count is stored in the stream, then each object in the array or list gets stored in turn. This same pattern is used for the list of folders in a project and the classes they contain.

Other things I discovered about ClassView, using the OpenVC Add-in and information stored in the stream, are:

Class items and the Globals folder are both implemented as containers of variables and functions, but as objects they are not serialized in our stream. They are probably generated from the source code each time we open the workspace and then associated to folders as specified by the ClassView stream.

Project and folder items, instead, are stored exactly the same way MFC implement serialization. When an object is serialized through CArchive::WriteObject, its RUNTIME_CLASS is serialized too, and both are written to the stream only the first time, using an index to reference them subsequent times. This way MFC avoids to duplicate RUNTIME_CLASS information and handles multiple references to the same object. The RUNTIME_CLASS is placed before the object to locate the CRuntimeClass::CreateObject function when the object is read back from the stream. Here follows a stripped down version of the WriteObject function:

void CArchive::WriteObject(const CObject* pOb)
{
    if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)
    {
        // save out index of already stored object

        *this << (WORD)nObIndex;
    }
    else
    {
        // write class of object first

        CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
        WriteClass(pClassRef);
        // enter in stored object table

        (*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
        // cause the object to serialize itself

        ((CObject*)pOb)->Serialize(*this);
    }
}

The objects in our stream are unique, so let's consider the alternative branch of the condition. The WriteClass function writes a WORD prefix defined as 0xFFFF, then it writes information sufficient to identify the RUNTIME_CLASS. In the same file (ARCOBJ.CPP), which is part of the MFC 6.0 sources, we can find the definitions of some constants that we can find in the ClassView stream:

#define wNewClassTag    ((WORD)0xFFFF)      
    // special tag indicating new CRuntimeClass

#define wClassTag       ((WORD)0x8000)      
    // 0x8000 indicates class tag (OR'd)

The first is the special prefix we have just seen, that precedes any new RUNTIME_CLASS definition. After this prefix we find the object schema as a WORD value (which is 0x0001 for project items and 0x0002 for folders), and the class name as a counted string with the character count also stored as a WORD. Take a look at the source, where inessential code has been removed:

void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
    if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)
    {
        // previously seen class, write out the index tagged by high bit

        *this << (WORD)(wClassTag | nClassIndex);
    }
    else
    {
        // store new class

        *this << wNewClassTag;
        pClassRef->Store(*this);
        // store new class reference in map

        (*m_pStoreMap)[(void*)pClassRef] = (void*)m_nMapCount++;
    }
}
void CRuntimeClass::Store(CArchive& ar) const
    // stores a runtime class description

{
    WORD nLen = (WORD)lstrlenA(m_lpszClassName);
    ar << (WORD)m_wSchema << nLen;
    ar.Write(m_lpszClassName, nLen*sizeof(char));
}

As you can see RUNTIME_CLASS information is stored only the first time, as with multiply referenced objects, while subsequent times only an WORD index is stored, OR'ed with the special value 0x8000, that we can see in the ClassView stream for projects and folders following the first.

After class information, either complete or with just the indexed reference, follows a list of folders for a project item and also a list of classes for a folder item. Serialization of folders, that can be nested, follows the same pattern of projects in a workspace, a DWORD count followed by serialization of each folder object in turn. Classes are stored more simply with their DWORD count followed by the name of each class as a counted string, with a BYTE length. I suspect that generic strings like these, but not the class names as we have seen above, are stored again using MFC provided functions and that is:

CArchive& operator<<(CArchive& ar, const CString& string)
{
    if (string.GetData()->nDataLength < 255)
    {
        ar << (BYTE)string.GetData()->nDataLength;
    }
    else if (string.GetData()->nDataLength < 0xfffe)
    {
        ar << (BYTE)0xff;
        ar << (WORD)string.GetData()->nDataLength;
    }
    else
    {
        ar << (BYTE)0xff;
        ar << (WORD)0xffff;
        ar << (DWORD)string.GetData()->nDataLength;
    }
    ar.Write(string.m_pchData, string.GetData()->nDataLength);
    return ar;
}

Unfortunately, I could not verify this hypothesis since my class names were always shorter than 255 characters and I also doubt the compiler would accept identifiers any longer than that, but the data in the stream is compatible with this code and it is another clue that MFC serialization support has been used for ClassView data.

As a last note, we can see that empty lists of folders and classes are stored with the only intial count set to zero. Indexed references to serialized classes use an integer variable that is incremented each time a new class or a new object is written to the stream, and the new version of this Add-in reflects this consideration, removing the unneeded <SLOB> tag from the XML format I used for the stream (old files are still interpreted correctly).

Add-In Usage

Using the add-in is pretty simple. See this page for installation instructions if you don't already know how to install add-ins. After installing it, the add-in is now ready to use. This is how the add-in's toolbar looks like:

Add-In default toolbar

You have three buttons:

  1. About RestoreClassView
  2. Restore ClassView folders
  3. Save ClassView folders

They all should be self-explaining. You may choose between binary (default) and XML format and your choice is recorded in the registry, under the key "HKCU\Software\The Code Project\RestoreClassViewAddin".

If you rename a class in your project you may lose all your workspace's folders. You can then use the XML file format to recover your folders: just replace the class name in the XML file and reload it with the RestoreClassView Add-in.

After all, that's what all this is meant to.

Updates

19 Jan 2004

Changed parsing routines and XML format, with a more robust restore operation from XML.

15 Apr 2002

Fixed a bug: the Restore procedure was always looking for the last saved file, regardless of the user's choice.
I forgot a sentence in the article.

Acknowledgements

This project is based on the following people's work:

My changes for Version 2.0 of the add-in:

Any comment or suggestion is appreciated.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralI need a class locate
lao1000
17:22 10 Aug '06  
Thank you very much first.
Sometimes , I forget the location of a class in the class folder.
If we can locate it, it is useful.
Smile
GeneralVery nice but bug..
Zigge
0:57 25 Feb '04  

Hi

When I press the 'Retrieve class view' button the MSDEV crashes after a long time (there's maybe 1200 classes), presumably when the view sheet is to be updated. The crash occur 100% of the times when retrieving, if the class view has been degenerated (i.e. no folders any more).

I have to kill the process in the task manager, and when restarting and loading the project, the class view is OK.

If I at this moment retrieve the class view once more, no crash occurs.

I use VC 6 SP 6. The .clv-file is checked out of SourceSafe. i.e. is not write protected.

/Chris
GeneralRe: Very nice but bug..
Paolo Messina
9:23 25 Feb '04  
So the first time it crashes and subsequent times it works ok? It may be because of a different OPT file.
The first time the OPT file has no ClassView folders, then the Addin copies the ClassView stream from the CLV to the OPT file, then VC++ crashes when reloading the workspace. At this time, though, the OPT file is different and a new Addin execution goes fine.

Well, I would have to debug such a situation. I don't know what could be wrong. Can you debug VC++ with another VC++ instance during the crash and analyze the situation?

Or can you send me the CLV file and the OPT file (before the first run and after the crash) in a zip archive by email?

BTW, does XML give you the same problems?

Thanks,
Paolo

------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)
GeneralAddIn and Service Pack?
Jens Scheidtmann
5:29 30 Jul '02  
Dear Paolo,

have you ever tried to use your AddIn with an updated VC++ 6.0?
I recently installed SP5 on my development system and after that from my workspace containing two projects (first has no subfolders) only one subfolder of the second project gets exported into the XML-file. On reading in an exception is thrown and VC crashes.

Can you reproduce this?

TIA

Jens Scheidtmann

--
Jens Scheidtmann
Technical Chemistry
University of Saarland

GeneralRe: AddIn and Service Pack?
Paolo Messina
13:05 30 Jul '02  
Thanks for this report. Unfortunately i don't have SP5 installed and I can't find the CD, so I'm not able to reproduce it.
Can you debug it and trace down the problem? Or send me the failing .opt file?

Thanks,
Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralRe: AddIn and Service Pack?
David Pritchard
7:55 16 Oct '02  
The same thing happens to me -- only the first subfolder is stored. I don't get the crash however when I try to open the XML file, though, just an "error opening the file". I also have SP5.

I'll try to look at the code.
GeneralSolved SP5 Problem
David Pritchard
9:32 16 Oct '02  
The solution appears to be simple. Recompile on your machine with SP5 and everything magically works. It's some sort of incompatibility between code generated with different SPs.
GeneralRe: Solved SP5 Problem
Paolo Messina
0:10 17 Oct '02  
Thanks David!

That sounds very weird, though. Unbelievable. Eek!
I thought it had something to do with the OPT file, maybe they changed the internal format a little bit for SP5 (added/removed something in their Serialize function).
To say the truth, my conversion code is not that robust, especially when reading XML, e.g.: I rely on the parent tags for the children's count (corrupted files I've seen had a count of X folders, but only Y child folder tags).

However, happy to know the solution is so simple. Big Grin
Now that I installed SP5, I'll update the article as soon as I find some time to recompile. Wink

Thanks again,
Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralRe: Solved SP5 Problem
Jan Marten
23:47 26 Jan '03  
Hi Paolo,

I ran into the same problem as David, only that recompiling under SP5 didn't solve it. I found a workaround, though: Inserting a dummy folder into each empty project makes the XML-Export run smoothly.
I hope, this piece of information will help you make your very helpful tool perfect.

Bye,
Oliver
GeneralRe: Solved SP5 Problem
Paolo Messina
4:40 27 Jan '03  
Hi Oliver,

I installed SP5, so how can I reproduce the bug?
Did you keep corrupted files (both CLV and XML)? Maybe I could do some tests on them...

Jan Marten wrote: I hope, this piece of information will help you make your very helpful tool perfect.
It's always nice to know your code is useful to many people. And I'd like to fix the bugs if possible. Smile

Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralRe: Solved SP5 Problem
Michael Schoenherr
2:37 7 Mar '03  
Hi Paolo,
first of all, thanks for this great AddIn. It's really helpful!

The same problem occurs in service pack 3.
I found a solution: I had to change all constants 0x8003 to 0x805B in file CLVParser.cpp. In my opinion it's possible that all used constant may change in every service pack.
Maybe you can supply a sort of autodetection or a configuration ability (via registry) if it's impossible .

BTW: I faced an other problem which occures if the list of projects not exactly matches the actual project list. DevStutio corrupts .opt file and
crashes in further workspace openings.
I think it should be possible, to scan the actual project list and do a merge of both information.

Michael
GeneralRe: Solved SP5 Problem
Paolo Messina
0:00 10 Mar '03  
Thank you Michael!

Michael Schoenherr wrote: I found a solution: I had to change all constants 0x8003 to 0x805B in file CLVParser.cpp. In my opinion it's possible that all used constant may change in every service pack.
Now I know where to look for... Wink

Michael Schoenherr wrote: I think it should be possible, to scan the actual project list and do a merge of both information.
Yes, it could be possible, but that's one of those things XML output is for. I think it's easier to run the tool again to obtain an up-to-date XML, then do a merge with your old one using some other tool or simply by hands.
However, integrated merging capability would be cool! Big Grin

Cheers,
Paolo

------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)
GeneralRe: Solved SP5 Problem
Paolo Messina
6:09 22 Jan '04  
Michael Schoenherr wrote: I found a solution: I had to change all constants 0x8003 to 0x805B in file CLVParser.cpp. In my opinion it's possible that all used constant may change in every service pack.
It is actually an index that depends on the number and order of projects and folders in your workspace. At least this is how the latest version handles it. Please try it out to see if it's correct. What's strange is that 0x5B is a very high index, that means you have over 80 projects in a workspace. If the latest version (import from XML) doesn't work for you, please send me the saved CLV (or original OPT), the saved XML and the OPT reconstructed from XML after you load it with the addin, so I could figure out what's wrong.

Thanks,
Paolo

------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)
GeneralRe: Solved SP5 Problem
Michael Schoenherr
23:31 26 Jan '04  
great!
now everything works fine, and yes the workspace currently contains over 120 projects and contains only one module of the
whole system. You can imagine how useful your addin is!!!

Thanks again.
GeneralSolved in ver 2.1
Paolo Messina
8:51 27 Jan '04  
Thanks Michael!

This is an important confirmation that I interpreted ClassView data correctly. I'm glad to hear that it's working fine now, and that it doesn't have anything to do with the applied service pack. Wink

Cheers,
Paolo

------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)
GeneralOops! Bug Fix
Paolo Messina
14:07 5 Apr '02  
I forgot a line in CCommands::Load()
if (IDOK == dlg.DoModal())
{
sFilePath = dlg.GetPathName(); // this was missing
if (dlg.GetFilterIndex() == 2)
bXML = TRUE;
else bXML = FALSE;
}
Something went wrong with Copy&Paste... Wink
I also forgot a sentence in the article. Blush

An update is coming... Smile

Cheers,
Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralAm I too late?
Uwe Keim
20:30 4 Apr '02  
An article dedicated to me Smile

Thank you very much! Just being downloading it. Yahoooooo! Big Grin

--
See me: www.magerquark.de
GeneralRe: Am I too late?
Paolo Messina
2:29 5 Apr '02  
Well, since you was the one who asked... Wink

I finally found some time to write up a small article for that. But, above all, I hope it works! Smile

Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralNice work.
Jignesh I. Patel
5:20 2 Apr '02  
Excellent, keep it up.

Jignesh
GeneralRe: Nice work.
Paolo Messina
0:07 3 Apr '02  
Thank you, Jignesh!

But half the glory is yours and Nick's. Smile

Paolo

------
"airplane is cool, but space shuttle is even better" (J. Kaczorowski)

GeneralRe: Nice work.
Nick Hodapp (MSFT)
10:42 22 Jan '04  
Rock on! Very nice...

Nick

This posting is provided “AS IS” with no warranties, and confers no rights. You assume all risk for your use. © 2003 Microsoft Corporation. All rights reserved.

GeneralRe: Nice work.
Paolo Messina
1:09 23 Jan '04  
Thanks Nick!

Your OpenVC addin is great, I like to see under the hood. I extended OpenVC to try to identify more things (like GDI and USER handles) and to display more information about identified objects. I also thought about using MFC serialization to read and write from the stream, but that would be a huge hack and after all my addin is behaving well enough (I hope). Wink

Cheers,
Paolo

------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)


Last Updated 22 Jan 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010