Программирование на Visual C++. Архив рассылки
Шрифт:
• список ни разу не вызывавшихся функций (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). Примечательна
И напоследок, несколько советов.
1. Не стоит профилировать все приложение целиком, лучше сконцентрироваться на каких-то отдельных частях, представляющих наибольший интерес. (См. параметры /EXC и /INC). Есть много частей, которые просто нет смысла профилировать – такие, как пользовательский интерфейс, например.
2. Замеры не всегда будут точными, поэтому имеет смысл брать среднее значение от нескольких проходов. Собирать статистику за несколько проходов можно с помощью опции Merge.
3. Когда вы профилируете, постарайтесь, чтобы количество запущенных процессов в системе было минимальным.
4. Лучше отсоединиться от локальной сети или интернета, чтобы ОС не приходилось принимать входящие пакеты.
5. Следите за количеством вызовов. Например, у вас в алгоритме есть итерация на тысячу повторений – проследите, чтобы функции, которыми он пользуется, вызывались соответствущее число раз.
6. Время, в течение которого выполняется функция, включает также и время, в течение которого выполнялись все функции, вызванные из данной.
Заинтересовавшимся данной темой предлагаю следующие статьи в MSDN:
• Performance Tuning
• Using Profile, PREP and PLIST
• Profiling from the Development Environment
Q. У меня вопрос для гуру. В конференциях от пару раз возникал, но как-то так тихо и кончался. То ли это очевидная истина, то ли никто не знает (чему я не верю). Итак вопрос: как в программу на правую кнопку подцепить меню, такое же как в Експлорере? Как туда напихать свои элементы? Второй второй вопрос отпадает, если можно выцепить именно меню, а не какую-то системную функцию, которая выводит окно меню, а тебе с этим сприходится смиряться, как с фактом бытия этого экранного элемента…
A1 Контекстное (правокнопочное) меню создать довольно просто:
1. Добавляем в графическом редакторе новое пустое меню.
2. Для крайнего слева элемента верхнего уровня вводим какое-нить имя и в полученное раскрывающееся меню добавляем команды.
3. Вставляем обработчик сообщения WM_CONTEXTMENU в класс "вид" или в класс другого окна, получающего сообщения от кнопок мыши, ну, например, в CMyDialog. Обработчик этот программируем так:
Вот и все. TrackPopupMenu и занимается выводом контекстного меню на экран. Правда, объект класса CMenu лучше сделать мембером класса, тогда в любой функции можно будет удалять, добавлять, запрещать etc. элементы меню. Конечно, в этом случае m_Menu.LoadMenu(IDR_MYMENU); надо написать в OnInitDialog. Заметте, OnContextMenu получает координаты курсора, т.е. можем для разных областей окна элементарно выводить разные меню, просто проверяя координаты.
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.
Функция, которая отображает контекстное меню, может выглядеть примерно так (я снабдил её подробными комментариями).
Эту функцию можно вызывать, например, из обработчика OnContextMenu. Делается это так:
За дополнительной информацией следует обратиться к следующим статьям в MSDN:
– Periodicals 1997, Microsoft Systems Journal, April, Wicked Code
– Knowledge Base, статья ID: Q198288
– Описание IShellFolder и IContextMenu
Что касается второго вопроса (о создании собственных пунктов меню), мы имеем полный контроль над процессом создания меню, а значит можем делать с ним всё, что угодно. Нужно только иметь в виду 2 момента.
Во-первых, поскольку функция TrackPopupMenu вызывается с флагом TPM_RETURNCMD, она на будет отправлять окну сообщение WM_COMMAND. Поэтому нужно анализировать значение nCmd, возвращённое функцией TrackPopupMenu и вызывать нужный обработчик вручную. Например:
Во-вторых, функция IContextMenu::QueryContextMenu получает параметры idCmdFirst, idCmdLast (в примере выше они равны 1 и 0x7FFF соответственно). Идентификаторы для стандартных пунктов меню выбираются именно в диапазоне от idCmdFirst до idCmdLast. Поэтому нужно проследить, чтобы идентификаторы пользовательских пунктов меню в этот диапазон не попали.
Когда начал читать вашу статью на тему мерцания, подумал было, что вы обязательно упомянете тот метод, который использовал я в своей программе. На мой взгляд, он достаточно известен, и, кажется, является самым самым лучшим.
Нужно просто создать обработчик события WM_ERASEBKGND с одной-единственной строчкой:
Т.е., по-русски говоря, программа фон не очистила, рисуйтесь полностью.
На ответ A1 из прошлого выпуска:
Теперь практика. Пусть имеется готовое SDI приложение (с технологией Документ/Представление). Создаем дополнительное Представление. Это делается в функции CFrameWnd::OnCreateClient примерно так:
Этот код работает неправильно, причём это видно даже невооружённым взглядом. В последней строчке функции CMainFrame::OnCreateClient вызывается функция базового класса. Но ведь поле pContext->pNewViewClass уже изменилось! В результате вместо двух разных видов будет создано два одинаковых. Ошибка лечится переносом вызова функции из базового класса в начало переопределённой функции:
Кроме того, неясно, как использовать функцию SwitchView. Указатель на созданный нами вид хранится в m_pNewView, но для получения указателя на вид, созданный самой MFC, не видно удобного способа. Вероятно, лучший вариант – также сохранить его в члене класса CMainFrame.