Основные моменты реализации
Сама программа CrashFinder является непосредственным MFC-приложением (т. е. напрямую использует библиотеку классов Microsoft Foundation Class), поэтому большая ее часть должна быть хорошо узнаваемой. Чтобы облегчить дальнейшее расширение программы CrashFinder (это можно сделать, следуя рекомендациям, приведенным в разделе "Что делать дальше с утилитой CrashFinder?" в конце этой главы), укажу на три ключевых момента и поясню основные особенности их реализации. Во-первых, речь пойдет о символьной машине, которую использует CrashFinder, во-вторых, мы рассмотрим, где выполняется основная работа в программе CrashFinder, и, наконец, опишем архитектуру данных этой программы.
CrashFinder использует символьную машину DBGHELP.DLL, описанную в главе 4. Единственной интересной деталью является то, что необходимо заставить эту машину загружать весь исходный файл и информацию о номерах строк, пересылая флажок SYMOPT_LOAD_LINES в функцию symsetoptions. Символьная машина DBGHELP.DLL по умолчанию не загружает исходный файл и информацию о номерах строк, так что об этом должен позаботиться программист.
Еще одна особенность реализации программы CrashFinder — вся работа, по существу, выполняется в документальном классе ccrashFinderDoc. Он содержит класс csyinboiEngine, осуществляет поиск всех символов и управляет их представлением (видом). Его ключевая функция— ccrashFinderDoc:: LoadAndShowimage — показана в листинге 8-2. Эта функция подтверждает правильность двоичного образа, проверяет его на наличие элементов проекта, имеющих конфликтующие адреса загрузки, загружает символы и вставляет образ в конец дерева. Она вызывается и при добавлении двоичного образа к проекту, и при открытии проекта. Перекладывая управление этими рутинными операциями на функцию CcrashFinderDoc: :LoadAndShowimage, разработчик может быть уверен, что основная логика программы CrashFinder всегда находится в одном месте, и что проект хранит только имена двоичных образов, а не копирует всю таблицу символов.
Листинг 8-2. Функция CcrashFinderDoc: :LoadAndShowimage
BOOL CcrashFinderDoc :: LoadAndShowimage ( CBinaryImage * plmage,
BOOL bModifiesDoc)
{
// Проверить предположения за пределами функции.
ASSERT ( this);
ASSERT ( NULL != m_pcTreeControl);
// Строка, которая может использоваться для любых сообщений
CString sMsg ;
// Состояние для графики дерева
int iState = STATEJSIOTVALID;
// Переменная для булевского возвращаемого значения
BOOL bRet ;
// Убедиться, что параметр — в порядке.
ASSERT ( NULL != plmage);
if ( NULL == plmage)
{
// Ничего не может случиться с плохим указателем,
return ( FALSE);
}
// Проверить, правилен ли этот образ. Если это так, убедиться,
// что его еще нет в списке и что он не имеет
// конфликтующего адреса загрузки. Если это не так, все равно
// добавить его, т. к. нехорошо просто выбрасывать данные
// пользователя.
// Если образ плох, я просто показываю его с неправильным растром
// и не загружаю в символьную машину,
if ( TRUE == p!mage->IsValidImage ())
{
// Здесь сканируются элементы массива данных, чтобы отыскать
// три проблемных условия:
// 1. Двоичный образ уже есть в списке. Если это так, то возможен
// только преждевременный выход.
// 2. Двоичный образ будет загружен в адрес, который уже
// есть в списке. Если это так, открываем диалоговое окно
// Properties для двоичного образа, чтобы перед его
// добавлением в список можно было изменить адрес загрузки.
// 3. Проект уже включает исполняемый (ЕХЕ-)образ, и plmage тоже
// является исполняемым.
// Для начала я всегда оптимистично предполагаю, что данные в
// plmage правильны.
BOOL bValid = TRUE;
int iCount = m_cDataArray.GetSize ();
for ( int i = 0; i < iCount; i++)
{
CBinaryImage * pTemp = (CBinaryImage *)m_cDataArray[ i ];
ASSERT ( NULL != pTemp);
if ( NULL = pTemp)
{
// Мало ли что может случиться с плохим указателем!
return ( FALSE);
}
// Согласованы ли два этих Cstring-значения?
if ( pImage->GetFullName О = pTemp->GetFullName ())
{
// Сообщить пользователю!!
sMsg.FormatMessage ( IDS_DUPLICATEFILE,
pTemp->GetFullName () );
AfxMessageBox ( sMsg);
return ( FALSE);
}
// Если текущее изображение из структуры данных неправильно,
// то проверим дублирование имен, как это было только что |
// показано, но адреса загрузки и характеристики ЕХЕ-образа
// проверить трудно. Если рТетр — неправильный,
//то следует пропустить эти проверки..
// Это может привести к проблемам, но т. к. рТетр отмечен
//в списке как неправильный, то переустановка
// свойств становится проблемой пользователя.
if ( TRUE == pTemp.->IsValidIinage ( FALSE) )
{
// Проверить, что в проект не добавлены два ЕХЕ-файла.
if ( 0 == ( IMAGE_FILE_DLL &
pTemp->GetCharacteristics ()))
{
if ( 0 = { IMAGE_FILE_DLL &
pImage->GetCharacteristics ()))
{
// Сообщить пользователю!!
SMsg.FormatMessage ( IDS_EXEALREADYINPROJECT,
p!mage->GetFullName (), pTemp->GetFullName () );
AfxMessageBox ( sMsg);
// Попытка загрузить два образа, помеченных как
// "ЕХЕ", будет автоматически отбрасывать данные
// для plmage. return ( FALSE);
}
}
// Проверить конфликты адресов загрузки,
if ( pImage->GetLoadAddress () == pTemp->GetLoadAddress() )
{
sMsg.FormatMessage ( IDS_DUPLICATELOADADDR ,
pImage->GetFullName () ,
pTemp->GetFullName () );
if ( IDYES == AfxMessageBox ( sMsg, MB_YESNO))
{
// Пользователь хочет изменить свойства вручную
pImage~>SetProperties ();
// Проверить, что адрес загрузки на самом деле
// изменился и что нет конфликта
//с другим двоичным образом.
int iIndex;
if ( TRUE =
IsConflictingLoadAddress (
pImage->GetLoadAddress(),
iIndex ) )
{
sMsg.FormatMessage
( IDS_DUPLICATELOADADDRFINAL,
p!mage->GetFullName () ,
((CBinaryImage*)m_cDataArray[iIndex])->GetFullName());
AfxMessageBox ( sMsg);
// Данные в pImage неправильные, поэтому
// двигаемся дальше и выходим из цикла.
bValid = FALSE;
break;
}
}
else
{
// Данные в plmage неправильные, поэтому
// двигаемся дальше и выходим из цикла.
bValid = FALSE;
break;
}
}
}
}
if ( TRUE = bValid)
{
// Этот образ хорош (по крайней мере, по отношению
//к загруженным символам).
iState = STATE_VALIDATED;
}
else
{
iState = STATE_NOTVALID;
}
}
else
{
// Этот образ неправильный.
iState = STATE_NOTVALID;
}
if ( STATE_VALIDATED = iState)
{
// Попытка загрузить этот образ в символьную машину.
bRet =
m_cSymEng.SymLoadModule(NULL ,
(PSTR)(LPCSTR)pImage->GetFullName(),
NULL
pImage->GetLoadAddress () ,
0 );
// Наблюдение закончено. SymLoadModule возвращает адрес загрузки
// образа, неравный TRUE.
ASSERT ( FALSE != bRet);
if ( FALSE == bRet)
{
TRACE ( "m_cSymEng.SymLoadModule failed!!\n");
iState = STATE_NOTVALID;
}
else
{
iState ь STATE_VALIDATED;
}
}
// Установить значение "Extra Data" для plmage в состояние загрузки
// отладочных символов i
f ( STATEJVALIDATED == iState)
{
pImage->SetExtraData ( TRUE);
}
else
{
pImage->SetExtraData ( FALSE);
}
// Поместить этот элемент в массив.
m_cDataArray.Add ( plmage);
// Добавлен ли элемент модификации документа?
if ( TRUE == bModifiesDoc)
{
SetModifiedFlag ();
}
CCrashFinderApp * pApp = (CCrashFinderApp*)AfxGetApp ();
ASSERT ( NULL != pApp);
// Поместить строку в дерево.
HTREEITEM hltem =
m_pcTreeControl->Insert!tem ( pApp->ShowFullPaths ()
? pImage->GetFullName ()
: pImage->GetName () ,
iState ,
iState );
ASSERT ( NULL != hltem);
// Поместить указатель на образ в данные элемента. Этот указатель
// облегчает обновление символьной информации модуля
// всякий раз, когда его вид претерпевает изменения.
bRet = m_pcTreeControl->SetItemData ( hltem, (DWORD)plmage);
ASSERT ( bRet);
// Форсировать выбор элемента.
bRet = m_pcTreeControl->SelectItem ( hltem);
// Bee OK, Jumpmaster!
return ( bRet);
}
И, наконец, опишем архитектуру данных программы CrashFinder. Ее главная структура данных — это просто массив классов cbinaryimage. Каждый класс cbinaryimage представляет отдельный двоичный образ, добавляемый к проекту, и обслуживает информацию о таких деталях этого образа, как адрес загрузки, двоичные свойства и имя. Документ1 добавляет объект cbinaryimage (двоичный образ) к массиву главных данных и помещает значение соответствующего указателя в слот данных узлового элемента дерева. При выборке элемента в представлении дерева двоичных файлов (в левой панели окна программы CrashFinder) выбранный узел пересылается назад в документ, так чтобы документ смог получить объект cbinaryimage и просмотреть его символьную информацию (предъявив ее пользователю в правой панели окна программы CrashFinder).
Точнее — объект класса CcrashFinderDoc. — Пер.