//*******************************************************************************
// COPYRIGHT NOTES
// ---------------
// You may use, compile or redistribute this source as part of your application
// for free. You may not redistribute it as a part of a software development
// library without the agreement of the author. If the sources are
// distributed along with the application, you should leave the original
// copyright notes in the source code without any changes.
// This code can be used WITHOUT ANY WARRANTIES on your own risk.
//
// Nick Hodapp
// nhodapp@codeveloper.com
//
//*******************************************************************************
// DlgClasses.cpp : implementation file
//
#include "stdafx.h"
#include "DlgClasses.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CDlgClasses dialog
CDlgClasses::CDlgClasses(CWnd* pParent /*=NULL*/)
: CDialog(CDlgClasses::IDD, pParent)
{
//{{AFX_DATA_INIT(CDlgClasses)
m_iMode = 0;
m_strItemCount = _T("");
m_strCode = _T("");
//}}AFX_DATA_INIT
m_classView.m_pmapClasses = &m_mapClassHierarchy;
}
void CDlgClasses::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CDlgClasses)
DDX_Control(pDX, IDC_ITEM_CT, m_staticCount);
DDX_Control(pDX, IDC_EDIT_CODE, m_editCode);
DDX_Radio(pDX, IDC_MODE_REPORT, m_iMode);
DDX_Text(pDX, IDC_ITEM_CT, m_strItemCount);
DDX_Text(pDX, IDC_EDIT_CODE, m_strCode);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CDlgClasses, CDialog)
//{{AFX_MSG_MAP(CDlgClasses)
ON_BN_CLICKED(IDC_MODE_REPORT, OnModeChange)
ON_WM_SIZE()
ON_WM_CREATE()
ON_NOTIFY_EX(GVN_SELCHANGED, IDC_GRID, OnSelChanged )
ON_BN_CLICKED(IDC_MODE_GRAPH, OnModeChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CDlgClasses::RecalcLayout()
{
//
// Quick & Dirty layout code.
// Ugly, yes.. But at least it's functional.
//
if (!GetDlgItem(IDOK))
return;
CRect rclClient; GetClientRect(&rclClient);
CRect rclBtn, rclCtl;
GetDlgItem(IDOK)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
GetDlgItem(IDOK)->SetWindowPos(NULL, rclClient.Width()-(rclBtn.Width()+10), rclBtn.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
GetDlgItem(IDCANCEL)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
GetDlgItem(IDCANCEL)->SetWindowPos(NULL, rclClient.Width()-(rclBtn.Width()+10), rclBtn.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
GetDlgItem(IDC_MODE_REPORT)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
GetDlgItem(IDC_MODE_REPORT)->SetWindowPos(NULL, rclClient.Width()-(rclBtn.Width()+10), rclBtn.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
GetDlgItem(IDC_MODE_GRAPH)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
GetDlgItem(IDC_MODE_GRAPH)->SetWindowPos(NULL, rclClient.Width()-(rclBtn.Width()+10), rclBtn.top, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
GetDlgItem(IDOK)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
rclClient.right = rclBtn.left - 10;
m_classView.SetWindowPos(NULL, 0, 0, rclClient.Width(), rclClient.Height(), SWP_NOZORDER | SWP_NOACTIVATE);
GetDlgItem(IDOK)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
rclClient.right = rclBtn.left - 10;
m_grid.SetWindowPos(NULL, 10, 10, (rclClient.Width()/2)-20, rclClient.Height()-25, SWP_NOZORDER | SWP_NOACTIVATE);
GetDlgItem(IDOK)->GetWindowRect(&rclBtn);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclBtn, 2);
rclClient.right = rclBtn.left - 10;
m_editCode.SetWindowPos(NULL, (rclClient.Width()/2), 10, (rclClient.Width()/2), rclClient.Height()-25, SWP_NOZORDER | SWP_NOACTIVATE);
m_grid.GetWindowRect(&rclCtl);
::MapWindowPoints(NULL, m_hWnd, (POINT*)&rclCtl, 2);
m_staticCount.SetWindowPos(NULL, 10, rclCtl.bottom+2, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
bool CDlgClasses::GetRuntimeInfo(void* pvObj, CRuntimeClass** pprc)
{
CObject* pObj = (CObject*)pvObj;
// We want to retrieve the MFC runtime type info pointer from this potential CObject pointer.
// The problem is, the pointer may not actually point to a CObject.
// If we just blindly cast and call pObj->GetRuntimeClass(),
// we're likely to try executing some non-code, or perhaps we'll call some
// other virtual function like a destructor.
// Since this would be bad, we attempt to check the signature of the function, to
// see if it "looks" like GetRuntimeClass(), which I've found to be simply a
// mov instruction followed by a ret instruction.
//
// I won't guarantee that this code will always work. If it starts failing, you fix it.
__try // hope that SEH works, and hope we don't need it!
{
// validate address:
if (FALSE == AfxIsValidAddress(pObj, sizeof(CObject), FALSE))
return false;
// check to make sure the VTable pointer is valid
void** vfptr = (void**)*(void**)pObj;
if (!AfxIsValidAddress(vfptr, sizeof(void*), FALSE))
return false;
// validate the first vtable entry
void* pvtf0 = vfptr[0];
if (IsBadCodePtr((FARPROC)pvtf0))
return false;
// look at the code for this function. validate it is a mov and ret
BYTE arrOpcodes[6];
memcpy(arrOpcodes, pvtf0, 6);
// prepare yourself, this gets ugly.
// if you don't understand whats going on then leave well enough alone.
// don't get all upset about it either.
if (arrOpcodes[0] == 0xFF && arrOpcodes[1] == 0x25) // jmp
{
void** pvAddr = *(void***)&(arrOpcodes[2]);
if (IsBadCodePtr((FARPROC)*pvAddr))
return false;
memcpy(arrOpcodes, *pvAddr, 6);
}
if (arrOpcodes[0] != 0xB8 || arrOpcodes[5] != 0xC3) // mov, ret
return false;
// ok, it looks like a likely candiate for a "real" GetRuntimeClass().
// go ahead and call it.
*pprc = pObj->GetRuntimeClass();
ASSERT(AfxIsValidAddress((*pprc)->m_lpszClassName, sizeof(char*), FALSE));
ASSERT((*pprc)->m_lpszClassName[0] == 'C'); // lame, but most classes will begin with 'C'
}
__except (1)
{
return false;
}
return true;
}
void CDlgClasses::Struct(DWORD dwUnique, CRuntimeClass* prc, DWORD dwOffset, const char* cpszType, bool bPointer, long lTypeSize, DWORD& dwUnknownCt)
{
// determine where we are, derivation-wise:
ASSERT(dwOffset < prc->m_nObjectSize);
CRuntimeClass* prcParent = prc->m_pfnGetBaseClass();
while (prcParent && dwOffset < prcParent->m_nObjectSize)
{
prc = prcParent;
prcParent = prcParent->m_pfnGetBaseClass();
}
// find the class entry:
M_Class::iterator it = m_mapClassInfo.find(CString(prc->m_lpszClassName));
if (it == m_mapClassInfo.end()) return;
SClass& cls = it->second;
// only one
if (cls.m_dwUnique != 0 && cls.m_dwUnique != dwUnique)
{
dwUnknownCt = 0;
return;
}
cls.m_dwUnique = dwUnique;
// struct begin?
if (dwOffset == 0 || (prcParent && dwOffset == prcParent->m_nObjectSize))
StructBegin(cls);
// unknown bytes?
if (dwUnknownCt && cpszType)
{
StructMember(cls, "BYTE", false, dwUnknownCt);
dwUnknownCt = 0;
}
// struct member:
if (cpszType)
StructMember(cls, cpszType, bPointer, 1);
// struct end?
if (dwOffset + lTypeSize >= prc->m_nObjectSize-1)
{
if (dwUnknownCt && NULL == cpszType)
{
StructMember(cls, "BYTE", false, dwUnknownCt);
dwUnknownCt = 0;
}
StructEnd(cls);
}
}
void CDlgClasses::StructBegin(SClass& cls)
{
// done before?
if (cls.m_strCode.GetLength())
return;
// get parent class name:
CString strDerivation;
if (cls.m_strParent.GetLength())
strDerivation.Format(": public %s", cls.m_strParent);
// format a struct:
CString strObj;
strObj = cls.m_strClass;
if (strObj.GetAt(0) == 'C')
strObj = strObj.Mid(1);
cls.m_strCode.Format("typedef struct tag%s %s\r\n{\r\n", strObj, strDerivation);
}
void CDlgClasses::StructMember(SClass& cls, const char* cpszType, bool bPointer, long lCount)
{
// format type name
CString strType = cpszType;
if (bPointer)
strType += "*";
// format variable name
CString strVar = cpszType;
if (strVar.GetAt(0) == 'C')
strVar = strVar.Mid(1);
if (bPointer)
strVar.Insert(0, 'p');
if (lCount > 1)
{
CString strArr; strArr.Format("[%ld]", lCount);
strVar.Insert(0, "arr");
strVar += strArr;
}
// format member:
CString strMember;
if (strType.GetLength() < 21)
strMember.Format(" %-21.21sm_%s;\r\n\r\n", strType, strVar);
else
strMember.Format(" %s\r\n%24.24sm_%s;\r\n\r\n", strType, " ", strVar);
// append:
cls.m_strCode += strMember;
}
void CDlgClasses::StructEnd(SClass& cls)
{
// close the struct
CString strObj = cls.m_strClass;
if (strObj.GetAt(0) == 'C')
strObj = strObj.Mid(1);
CString strCode;
strCode.Format("} S%s;\r\n\t", strObj);
cls.m_strCode += strCode;
}
void CDlgClasses::AnalyzeObject(CObject* pObj)
{
// validate
if (NULL == pObj || FALSE == AfxIsValidAddress(pObj, sizeof(CObject), FALSE))
return;
static DWORD dwUnique = 0;
DWORD dwLocal = ++dwUnique;
// look for sub-objects:
CRuntimeClass* prc = pObj->GetRuntimeClass();
BYTE *pbBegin = reinterpret_cast<BYTE*>(pObj),
*pbCur = reinterpret_cast<BYTE*>(pObj),
*pbEnd = pbCur+prc->m_nObjectSize;
DWORD dwMember(0),
dwRunLen(0),
dwInc(0);
// "typedef struct tagObjectName : public ParentName {"
while (pbCur < pbEnd)
{
CRuntimeClass* prcSub;
if ((pbCur-pbBegin > 0) && GetRuntimeInfo(pbCur, &prcSub)) // embedded object
{
// document contained object:
Struct(dwLocal, prc, pbCur-pbBegin, prcSub->m_lpszClassName, false, prcSub->m_nObjectSize, dwRunLen);
// catalog this contained object:
InsertClass(this, prcSub, (CObject*)pbCur);
// skip past the contained object:
dwInc = prcSub->m_nObjectSize;
}
else
if (GetRuntimeInfo((void*)*(long*)pbCur, &prcSub)) // embedded pointer to object
{
// document contained object pointer:
Struct(dwLocal, prc, pbCur-pbBegin, prcSub->m_lpszClassName, true, 4, dwRunLen);
// catalog this contained object pointer:
InsertClass(this, prcSub, (CObject*)*(DWORD*)pbCur);
// skip past the contained pointer:
dwInc = 4;
}
else
{
// continue searching:
dwInc = 1;
dwRunLen++;
Struct(dwLocal, prc, pbCur-pbBegin, NULL, false, 0, dwRunLen);
}
pbCur += dwInc;
}
if (dwRunLen)
Struct(dwLocal, prc, pbCur-pbBegin, NULL, false, 0, dwRunLen);
}
BOOL CDlgClasses::EnumClasses(HWND hwnd, LPARAM lParam)
{
CDlgClasses* pThis = (CDlgClasses*)lParam;
CWnd* pWnd = CWnd::FromHandle(hwnd);
if (pWnd)
{
pThis->InsertClass(pThis, pWnd->GetRuntimeClass(), pWnd);
EnumChildWindows(pWnd->GetSafeHwnd(), EnumClasses, lParam);
}
return TRUE;
}
void CDlgClasses::InsertClassRow(CRuntimeClass* prc, unsigned char ucLevel)
{
// class
int iRow = m_grid.InsertRow(prc->m_lpszClassName);
m_vecLevels.push_back(ucLevel & 0x7F);
// total byte count:
CString strCell;
strCell.Format("%ld", prc->m_nObjectSize);
m_grid.GetCell(iRow, 1)->SetText(strCell);
// non-derived byte count:
CRuntimeClass* prcParent = (prc->m_pfnGetBaseClass)();
if (prcParent)
{
strCell.Format("%ld", prc->m_nObjectSize - prcParent->m_nObjectSize);
m_grid.GetCell(iRow, 2)->SetText(strCell);
InsertClassRow(prcParent, ucLevel+1);
}
// schema
strCell.Format("%ld", prc->m_wSchema);
m_grid.GetCell(iRow, 3)->SetText(strCell);
}
void CDlgClasses::InsertClass(CDlgClasses* pThis, CRuntimeClass* pClass, CObject* pObj)
{
// must have class!
if (NULL == pClass)
return;
// inserted yet?
CString strClass = pClass->m_lpszClassName;
M_Class::iterator it = m_mapClassInfo.find(strClass);
if (it != m_mapClassInfo.end() && it->second.m_strCode != "")
return;
SClass& cls = it->second;
// parent name
CString strParent;
CRuntimeClass* pParentClass = (pClass->m_pfnGetBaseClass)();
if (pParentClass)
strParent = pParentClass->m_lpszClassName;
// store
if (true == pThis->m_mapClassInfo.insert(M_Class::value_type(strClass, SClass(strClass, strParent, pClass))).second)
pThis->m_mapClassHierarchy.insert(M_String::value_type(strParent, strClass));
// insert parent
InsertClass(pThis, (pClass->m_pfnGetBaseClass)());
// analyze the object itself for contained data members:
if (!cls.m_bAnalyzed && pObj)
{
cls.m_bAnalyzed = true;
AnalyzeObject(pObj);
}
// traverse any CPtrLists
if (pObj && pObj->IsKindOf(RUNTIME_CLASS(CPtrList)))
{
CPtrList* pList = (CPtrList*)pObj;
for (POSITION pos = pList->GetHeadPosition() ; pos ; )
{
void* pvObj = pList->GetNext(pos);
CRuntimeClass* prc;
if (GetRuntimeInfo(pvObj, &prc))
InsertClass(pThis, prc, (CObject*)pvObj);
}
}
else
// traverse any CObLists
if (pObj && pObj->IsKindOf(RUNTIME_CLASS(CObList)))
{
CObList* pList = (CObList*)pObj;
for (POSITION pos = pList->GetHeadPosition() ; pos ; )
{
CObject* pObj = pList->GetNext(pos);
InsertClass(pThis, pObj->GetRuntimeClass(), pObj);
}
}
else
// traverse any CPtrArrays
if (pObj && pObj->IsKindOf(RUNTIME_CLASS(CPtrArray)))
{
CPtrArray* pArr = (CPtrArray*)pObj;
for (DWORD dw = 0 ; dw < pArr->GetSize() ; dw++)
{
void* pvObj = pArr->GetAt(dw);
CRuntimeClass* prc;
if (GetRuntimeInfo(pvObj, &prc))
InsertClass(pThis, prc, (CObject*)pvObj);
}
}
else
// traverse any CObArrays
if (pObj && pObj->IsKindOf(RUNTIME_CLASS(CObArray)))
{
CObArray* pArr = (CObArray*)pObj;
for (DWORD dw = 0 ; dw < pArr->GetSize() ; dw++)
{
CObject* pObj = pArr->GetAt(dw);
InsertClass(pThis, pObj->GetRuntimeClass(), pObj);
}
}
}
void CDlgClasses::Refresh()
{
CWaitCursor wc;
// clear out everything:
m_mapClassInfo.clear();
m_mapClassHierarchy.clear();
m_grid.DeleteAllItems();
m_grid.InsertColumn("Name", DT_LEFT|DT_VCENTER|DT_SINGLELINE);
m_grid.InsertColumn("Total Bytes");
m_grid.InsertColumn("Non-derived Bytes");
m_grid.InsertColumn("Schema");
m_grid.SetFixedColumnCount(1);
m_grid.SetFixedRowCount(1);
// walk through the simple list of registered classes
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL; pDLL = pDLL->m_pNextDLL)
{
for (CRuntimeClass* pClass = pDLL->m_classList.GetHead() ; pClass != NULL; pClass = pClass->m_pNextClass)
InsertClass(this, pClass);
}
// enum the document-template classes:
CWinApp* pApp = AfxGetApp();
POSITION pos = pApp->GetFirstDocTemplatePosition();
while (pos)
{
CUnprotectedDocTemplate* pDocTemplate = (CUnprotectedDocTemplate*)pApp->GetNextDocTemplate(pos);
pDocTemplate->InsertClasses(this);
}
// now pick up any stray window classes:
EnumChildWindows(NULL, EnumClasses, (long)this);
// add to grid:
CString strCell;
m_vecLevels.clear();
for (M_Class::iterator it = m_mapClassInfo.begin() ; it != m_mapClassInfo.end() ; it++)
{
SClass& cls = it->second;
InsertClassRow(cls.m_prcInfo, 1);
}
m_gridTreeCol.TreeSetup(&m_grid, // tree acts on a column in this grid
0, // which column has tree
m_vecLevels.size(), // total number of rows if tree totally expanded
1, // Set fixed row count now, too
&(m_vecLevels[0]), // Tree Level data array --
// must have aiTotalRows of entries
TRUE, // T=show tree (not grid) lines; F=no tree lines
FALSE); // T=use 1st 3 images from already set image list
// to display folder graphics
m_gridTreeCol.SetTreeLineColor( RGB( 0, 0, 0xFF) );
m_gridTreeCol.TreeDisplayOutline( 1);
m_grid.AutoSizeColumn(0, m_grid.GetAutoSizeStyle());
m_grid.AutoSizeColumn(1, m_grid.GetAutoSizeStyle());
m_grid.AutoSizeColumn(2, m_grid.GetAutoSizeStyle());
m_grid.AutoSizeColumn(3, m_grid.GetAutoSizeStyle());
m_grid.Refresh();
m_strItemCount.Format("%ld Classes", m_mapClassInfo.size());
UpdateData(FALSE);
}
/////////////////////////////////////////////////////////////////////////////
// CDlgClasses message handlers
void CDlgClasses::PostNcDestroy()
{
CDialog::PostNcDestroy();
// modeless & allocated off the heap:
delete this;
}
int CDlgClasses::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
// graph create:
CRect rcl(0,0,10,10);
m_classView.Create(NULL, "", WS_CHILD | WS_HSCROLL | WS_VSCROLL, rcl, this, IDC_GRAPH);
// grid create:
m_grid.Create(rcl, this, IDC_GRID);
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
return 0;
}
BOOL CDlgClasses::OnInitDialog()
{
CDialog::OnInitDialog();
// graph init:
m_classView.OnInitialUpdate();
// grid init:
m_grid.SetTextBkColor(RGB(0xFF, 0xFF, 0xE0));
m_grid.SetRowResize(FALSE);
m_grid.SetColumnResize(TRUE);
m_grid.SetEditable(FALSE);
// code font:
m_courier.CreatePointFont(60, "Courier");
GetDlgItem(IDC_EDIT_CODE)->SetFont(&m_courier);
RecalcLayout();
Refresh();
return TRUE;
}
void CDlgClasses::OnOK()
{
Refresh();
}
void CDlgClasses::OnCancel()
{
// we're modeless:
DestroyWindow();
}
void CDlgClasses::OnModeChange()
{
UpdateData(TRUE);
// show / hide controls:
m_grid.EnableWindow(m_iMode == 0);
m_grid.ShowWindow(m_iMode == 0 ? SW_SHOW : SW_HIDE);
m_editCode.EnableWindow(m_iMode == 0);
m_editCode.ShowWindow(m_iMode == 0 ? SW_SHOW : SW_HIDE);
m_staticCount.EnableWindow(m_iMode == 0);
m_staticCount.ShowWindow(m_iMode == 0 ? SW_SHOW : SW_HIDE);
m_classView.EnableWindow(m_iMode == 1);
m_classView.ShowWindow(m_iMode == 1 ? SW_SHOW : SW_HIDE);
}
void CDlgClasses::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
RecalcLayout();
}
BOOL CDlgClasses::OnSelChanged( UINT id, NMHDR * pNotifyStruct, LRESULT * result )
{
NM_GRIDVIEW* pnmgv = (NM_GRIDVIEW*)pNotifyStruct;
// find and display "code" for selected item:
CGridCellBase* pCell = m_grid.GetCell(pnmgv->iRow, 0);
if (pCell)
{
CString strClass = pCell->GetText();
M_Class::iterator it = m_mapClassInfo.find(strClass);
if (it != m_mapClassInfo.end())
{
m_strCode = it->second.m_strCode;
UpdateData(FALSE);
}
}
return TRUE;
}