Чтение онлайн

на главную - закладки

Жанры

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:

• список ни разу не вызывавшихся функций (function coverage),

• запись содержимого стека при каждом вызове функции (function attribution).

Профилирование по строкам используется для проверки алгоритмов, т.к. позволяет посмотреть, сколько раз была выполнена каждая строчка, а также выявить строки, не выполнившиеся вообще ни разу. Здесь есть только два варианта: подсчет строк (line counting) – т.е. сколько раз данная строка была выполнена; и покрытие строк (line coverage) – показывает те строки, которые выполнялись хотя бы раз.

Перейдем к практике. Если у вас установлен Visual C++ Professional или Enterprise Edition, то профилировщик у вас есть, он встроен в IDE. Осталось только научиться им пользоваться. Предугадывая поток писем, замечу, что существует довольно большой выбор всяческих профилировщиков от сторонних фирм, возможности которых иногда действительно впечатляют. Но в данной статье я кратко рассмотрю возможности профилирования, встроенные в Visual C++.

Прежде всего необходимо установить опции проекта для включения профилирования (т.е. генерации профилировочной информации). Это делается через Project Settings|Link|Enable Profiling.

Дальше выберите Build|Profile, и появится диалог "Profile", где можно выбрать любой тип профилирования, плюс еще есть возможность как следует это все настроить с помощью Custom Options (см. параметры команды PREP). Примечательна

также опция Merge – позволяет совместить текущие результаты с предыдущими для наглядного сравнения. После нажатия на "OK" запускается ваша программа – дальше вы производите те действия, которые вам необходимо проверить. По завершении работы вашего приложения, профилировочная информация выводится в Profile Output Window, где вы ее анализируете… и делаете выводы.

И напоследок, несколько советов.

1. Не стоит профилировать все приложение целиком, лучше сконцентрироваться на каких-то отдельных частях, представляющих наибольший интерес. (См. параметры /EXC и /INC). Есть много частей, которые просто нет смысла профилировать – такие, как пользовательский интерфейс, например.

2. Замеры не всегда будут точными, поэтому имеет смысл брать среднее значение от нескольких проходов. Собирать статистику за несколько проходов можно с помощью опции Merge.

3. Когда вы профилируете, постарайтесь, чтобы количество запущенных процессов в системе было минимальным.

4. Лучше отсоединиться от локальной сети или интернета, чтобы ОС не приходилось принимать входящие пакеты.

5. Следите за количеством вызовов. Например, у вас в алгоритме есть итерация на тысячу повторений – проследите, чтобы функции, которыми он пользуется, вызывались соответствущее число раз.

6. Время, в течение которого выполняется функция, включает также и время, в течение которого выполнялись все функции, вызванные из данной.

Заинтересовавшимся данной темой предлагаю следующие статьи в MSDN:

• Performance Tuning

• Using Profile, PREP and PLIST

• Profiling from the Development Environment

ВОПРОС-ОТВЕТ

Q. У меня вопрос для гуру. В конференциях от пару раз возникал, но как-то так тихо и кончался. То ли это очевидная истина, то ли никто не знает (чему я не верю). Итак вопрос: как в программу на правую кнопку подцепить меню, такое же как в Експлорере? Как туда напихать свои элементы? Второй второй вопрос отпадает, если можно выцепить именно меню, а не какую-то системную функцию, которая выводит окно меню, а тебе с этим сприходится смиряться, как с фактом бытия этого экранного элемента…

Serg Loginov

A1 Контекстное (правокнопочное) меню создать довольно просто:

1. Добавляем в графическом редакторе новое пустое меню.

2. Для крайнего слева элемента верхнего уровня вводим какое-нить имя и в полученное раскрывающееся меню добавляем команды.

3. Вставляем обработчик сообщения WM_CONTEXTMENU в класс "вид" или в класс другого окна, получающего сообщения от кнопок мыши, ну, например, в CMyDialog. Обработчик этот программируем так:

CMyDialog::OnContextMenu(CWnd* pWnd, CPoint point) {

 CMenu menu;

 menu.LoadMenu(IDR_MYMENU);

 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);

}

Вот и все. TrackPopupMenu и занимается выводом контекстного меню на экран. Правда, объект класса CMenu лучше сделать мембером класса, тогда в любой функции можно будет удалять, добавлять, запрещать etc. элементы меню. Конечно, в этом случае m_Menu.LoadMenu(IDR_MYMENU); надо написать в OnInitDialog. Заметте, OnContextMenu получает координаты курсора, т.е. можем для разных областей окна элементарно выводить разные меню, просто проверяя координаты.

Sergey Pochechuev

A2 В Windows все файлы и папки входят в иерархию объектов оболочки. В неё также входят и объекты, не имеющие отношение к файловой системе: корзина, рабочий стол и т. п. Windows Explorer является по сути программой для просмотра этой иерархии объектов.

Каждый объект оболочки обязан реализовывать COM-интерфейс IShellFolder. Многие объекты реализуют и ряд других интерфейсов. Так, IExtractIcon отвечает за иконку объекта, а IContextMenu – за его контекстное меню. Эксплорер использует эти (и другие) интерфейсы, чтобы корректно отображать элементы иерархии объектов и позволять пользователю манипулировать ими. Мы также можем воспользоваться этими интерфейсами.

Итак, нам требуется функция, которая выводила бы контекстное меню для заданного файла (каталога), который передавался бы ей в качестве параметра. Эта функция должна выполнить следующие действия:

• Получить интерфейс IContextMenu для этого файла (каталога).

• Создать всплывающее меню (посредством CreatePopupMenu).

• Заполнить его элементами с помощью IContextMenu::QueryContextMenu.

• Показать меню пользователю (TrackPopupMenu).

• Выполнить выбранную команду посредством IContextMenu::InvokeCommand.

Основную сложность на самом деле представляет первый этап. Получить указатель на IContextMenu мы можем только, имея указатель на базовый интерфейс IShellFolder, но Windows не предоставляет простого способа получить этот указатель. Выполнение этой задачи в свою очередь распадается на несколько шагов:

– Получить интерфейс IShellFolder рабочего стола посредством SHGetDesktopFolder.

– Построить LPITEMIDLIST для заданного файла (каталога), используя IShellFolder::ParseDisplayName.

– Получить IShellFolder для этого файла вызовом IShellFolder::BindToObject.

Функция, которая отображает контекстное меню, может выглядеть примерно так (я снабдил её подробными комментариями).

void ShowContextMenu (CWnd *pWnd, LPCTSTR pszPath, CPoint point) {

 // Строим полное имя.

 TCHAR tchPath[MAX_PATH];

 GetFullPathName(pszPath, sizeof(tchPath)/sizeof(TCHAR), tchPath, NULL);

 // Если нужно, перекодируем ANSI в UNICODE.

 WCHAR wchPath[MAX_PATH];

 if(IsTextUnicode (tchPath, lstrlen (tchPath), NULL)) lstrcpy ((char *)wchPath, tchPath);

 else MultiByteToWideChar(CP_ACP, 0, pszPath, -1, wchPath, sizeof(wchPath)/sizeof(WCHAR));

 // Получаем интерфейс IShellFolder рабочего стола

 IShellFolder *pDesktopFolder;

 SHGetDesktopFolder(&pDesktopFolder);

 // Преобразуем путь в LPITEMIDLIST

 LPITEMIDLIST pidl;

 pDesktopFolder->ParseDisplayName(pWnd->m_hWnd, NULL, wchPath, NULL, &pidl, NULL);

 //
Получаем интерфейс IShellFolder для заданного файла (папки)

 IShellFolder *pFolder;

 pDesktopFolder->BindToObject(pidl, NULL, IID_IShellFolder, (void**)&pFolder);

 // Получаем интерфейс IContextMenu для заданного файла (папки)

 IContextMenu *pContextMenu;

 pFolder->GetUIObjectOf(pWnd->m_hWnd, 1, (LPCITEMIDLIST*)&pidl, IID_IContextMenu, NULL, (void**)&pContextMenu);

 // Создаём меню

 CMenu PopupMenu;

 PopupMenu.CreatePopupMenu;

 // Заполняем меню

 pContextMenu->QueryContextMenu(PopupMenu.m_hMenu, 0, 1, 0x7FFF,CMF_EXPLORE);

 // Отображаем меню

 UINT nCmd = PopupMenu.TrackPopupMenu(TPM_LEFTALIGN|TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, point.x, point.y, pWnd);

 // Выполняем команду (если она была выбрана)

 if(nCmd) {

CMINVOKECOMMANDINFO ici;

ZeroMemory(&ici, sizeof(CMINVOKECOMMANDINFO));

ici.cbSize = sizeof(CMINVOKECOMMANDINFO);

ici.hwnd = pWnd->m_hWnd;

ici.lpVerb = MAKEINTRESOURCE(nCmd-1);

ici.nShow = SW_SHOWNORMAL;

ContextMenu->InvokeCommand(&ici);

 }

 // Получаем интерфейс IMalloc.

 IMalloc *pMalloc;

 SHGetMalloc(&pMalloc);

 // Используем его для освобождения памяти, выделенной на ITEMIDLIST

 pMalloc->Free(pidl);

 // Освобождаем все полученные интерфейсы

 pDesktopFolder->Release;

 pFolder->Release;

 pContextMenu->Release;

 pMalloc->Release;

 return;

}

Эту функцию можно вызывать, например, из обработчика OnContextMenu. Делается это так:

void CMyView::OnContextMenu(CWnd* pWnd, CPoint point) {

 ShowContextMenu(pWnd, "C:\\command.com", point);

}

За дополнительной информацией следует обратиться к следующим статьям в MSDN:

– Periodicals 1997, Microsoft Systems Journal, April, Wicked Code

– Knowledge Base, статья ID: Q198288

– Описание IShellFolder и IContextMenu

Что касается второго вопроса (о создании собственных пунктов меню), мы имеем полный контроль над процессом создания меню, а значит можем делать с ним всё, что угодно. Нужно только иметь в виду 2 момента.

Во-первых, поскольку функция TrackPopupMenu вызывается с флагом TPM_RETURNCMD, она на будет отправлять окну сообщение WM_COMMAND. Поэтому нужно анализировать значение nCmd, возвращённое функцией TrackPopupMenu и вызывать нужный обработчик вручную. Например:

UINT nCmd = PopupMenu.TrackPopupMenu(…);

if (nCmd) {

 if (nCmd == 0x8000) {

AfxMessageBox("It works!!!");

 } else {

// Используем IContextMenu::InvokeCommand

 }

}

Во-вторых, функция IContextMenu::QueryContextMenu получает параметры idCmdFirst, idCmdLast (в примере выше они равны 1 и 0x7FFF соответственно). Идентификаторы для стандартных пунктов меню выбираются именно в диапазоне от idCmdFirst до idCmdLast. Поэтому нужно проследить, чтобы идентификаторы пользовательских пунктов меню в этот диапазон не попали.

Alexander Shargin
ОБРАТНАЯ СВЯЗЬ

Когда начал читать вашу статью на тему мерцания, подумал было, что вы обязательно упомянете тот метод, который использовал я в своей программе. На мой взгляд, он достаточно известен, и, кажется, является самым самым лучшим.

Нужно просто создать обработчик события WM_ERASEBKGND с одной-единственной строчкой:

BOOL CSomeClass::OnEraseBkgnd(CDC* pDC) {

 return FALSE;

}

Т.е., по-русски говоря, программа фон не очистила, рисуйтесь полностью.

Vlad

На ответ A1 из прошлого выпуска:

Теперь практика. Пусть имеется готовое SDI приложение (с технологией Документ/Представление). Создаем дополнительное Представление. Это делается в функции CFrameWnd::OnCreateClient примерно так:

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) {

 // class CNewView – это наше новое представление

 pContext->m_pNewViewClass = RUNTIME_CLASS(CNewView);

 // обратите внимание на идентификатор нового Представления

 // переменная m_pNewView описанна в CMainFrame как

 CNewView* m_pNewView;

 m_pNewView = STATIC_DOWNCAST(CNewView, CreateView(pContext, AFX_IDW_PANE_FIRST+1));

 m_pNewView->ShowWindow(SW_HIDE); // для сброса флага WS_VISIBLE

 return CFrameWnd::OnCreateClient(lpcs, pContext);

}

Этот код работает неправильно, причём это видно даже невооружённым взглядом. В последней строчке функции CMainFrame::OnCreateClient вызывается функция базового класса. Но ведь поле pContext->pNewViewClass уже изменилось! В результате вместо двух разных видов будет создано два одинаковых. Ошибка лечится переносом вызова функции из базового класса в начало переопределённой функции:

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) {

 int nResult = CFrameWnd::OnCreateClient(lpcs, pContext);

 …

 return nResult;

}

Кроме того, неясно, как использовать функцию SwitchView. Указатель на созданный нами вид хранится в m_pNewView, но для получения указателя на вид, созданный самой MFC, не видно удобного способа. Вероятно, лучший вариант – также сохранить его в члене класса CMainFrame.

Alexander Shargin
Поделиться:
Популярные книги

Усадьба леди Анны

Ром Полина
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Усадьба леди Анны

Чужая дочь

Зика Натаэль
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Чужая дочь

Светлая тьма. Советник

Шмаков Алексей Семенович
6. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
сказочная фантастика
фэнтези
5.00
рейтинг книги
Светлая тьма. Советник

Двойник Короля

Скабер Артемий
1. Двойник Короля
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Двойник Короля

Его нежеланная истинная

Кушкина Милена
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Его нежеланная истинная

Последний Паладин. Том 2

Саваровский Роман
2. Путь Паладина
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Последний Паладин. Том 2

Измена. Наследник для дракона

Солт Елена
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Измена. Наследник для дракона

Идеальный мир для Лекаря 9

Сапфир Олег
9. Лекарь
Фантастика:
боевая фантастика
юмористическое фэнтези
6.00
рейтинг книги
Идеальный мир для Лекаря 9

Мастер темных Арканов

Карелин Сергей Витальевич
1. Мастер темных арканов
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Мастер темных Арканов

Адвокат империи

Карелин Сергей Витальевич
1. Адвокат империи
Фантастика:
городское фэнтези
попаданцы
фэнтези
5.75
рейтинг книги
Адвокат империи

Кодекс Охотника. Книга XXI

Винокуров Юрий
21. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга XXI

Вечный. Книга II

Рокотов Алексей
2. Вечный
Фантастика:
боевая фантастика
попаданцы
рпг
5.00
рейтинг книги
Вечный. Книга II

Законы Рода. Том 3

Flow Ascold
3. Граф Берестьев
Фантастика:
фэнтези
аниме
5.00
рейтинг книги
Законы Рода. Том 3

Наследник

Шимохин Дмитрий
1. Старицкий
Приключения:
исторические приключения
5.00
рейтинг книги
Наследник