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

Источник: codingclub

Вопрос Мне попадался C++-код, где для неиспользуемых параметров применяется UNREFERENCED_PARAMETER, например:

int SomeFunction(int arg1, int arg2){ UNREFERENCED_PARAMETER(arg2) ...}

Но встречался и такой код:

int SomeFunction(int arg1, int /* arg2 */){ ...}

Не могли бы вы пояснить, в чем тут разница и что лучше?

Джуди Макгео (Judy McGeough)

Ответ Ну конечно. Начнем с UNREFERENCED_PARAMETER. Это макрос, определенный в winnt.h:

#define UNREFERENCED_PARAMETER(P) (P)

Иначе говоря, UNREFERENCED_PARAMETER раскрывается в передаваемый параметр или выражение. Его предназначение - избежать предупреждений компилятора о наличии параметров, на которые нет ссылок (неиспользуемых параметров). Многие программисты, включая вашего верного слугу, предпочитают компилировать при наивысшем уровне предупреждений, Level 4 (/W4). Предупреждения Level 4 попадают в категорию того, что можно спокойно игнорировать. Небольшие погрешности не повредят вашему коду, но могут создать о вас плохое впечатление. Например, вы написали где-то в своей программе строку вроде:

int x=1;

Однако вы нигде не используете x. Возможно, эта строка осталась с тех пор, когда вы действительно использовали x, но потом удалили часть кода, а об этой переменной забыли. Предупреждения Level 4 помогают обнаруживать такие мелкие промахи. Так почему бы не разрешить компилятору помочь вам в достижении выс-шего уровня профессионализма? Успешная компиляция с предупреждениями Level 4 позволяет гордиться своей работой. Проблема в том, что при Level 4 компилятор жалуется на совсем безобидные вещи, например на неиспользуемые параметры (конечно, они безобидны, только если вы действительно ими не пользуетесь). Допустим, у вас есть функция с двумя аргументами, но вы используете лишь один из них:

int SomeFunction(int arg1, int arg2)
{
return arg1+5;
}

При /W4 компилятор сообщит: "warning C4100: 'arg2': unreferenced formal parameter". Чтобы обмануть компилятор, можно добавить UNREFERENCED_PARAMETER(arg2). Теперь ваша функция ссылается на arg2, и компилятор заткнется. А поскольку выражение:

arg2;

ничего не делает, компилятор не будет генерировать для него никакого кода, поэтому вы не проиграете ни в размере, ни в эффективности.

Острые умы могут поинтересоваться: если arg2 не используется, то зачем вообще объявлять его? Обычно так делают, когда реализуют функцию, которая должна отвечать определенной сигнатуре API, спущенной свыше. Например, у MFC-обработчика OnSize должна быть следующая сигнатура:

void OnSize(UINT nType, int cx, int cy);

Здесь cx и cy - новые ширина и высота окна, а nType - некий код наподобие SIZE_MAXIMIZED, если окно должно быть полностью развернуто, или SIZE_RESTORED при нормальном размере. Как правило, nType вас не волнует - вы заботитесь только о cx и cy. Поэтому при компиляции с ключом /W4 вам понадобится UNREFERENCED_PARAMETER(nType). А ведь OnSize - лишь одна из тысяч функций в MFC и Windows. Так что довольно трудно написать Windows-программу без параметров, на которые нет ссылок.

Ну и хватит о UNREFERENCED_PARAMETER. Как Джуди заметила, задавая свой вопрос, еще один часто применяемый C++-программистами трюк, который позволяет добиться того же результата, - заключение лишнего для них параметра в сигнатуре функции в признаки комментария:

void CMyWnd::OnSize(UINT /* nType */,
int cx, int cy)
{
}

Теперь nType является безымянным параметром; то же самое вы получили бы, набрав OnSize(UINT, int cx, int cy). А сейчас вопрос за 64 000 долларов: каким способом вы бы воспользовались - безымянным параметром или UNREFERENCED_PARAMETER?

Обычно это не имеет никакого значения; выбор определяется стилем. (Вы любите кофе черный или со сливками?) Но я могу придумать по меньшей мере одну ситуацию, где нужен именно UNREFERENCED_PARAMETER. Допустим, вы решили запретить развертывание вашего окна на весь рабочий стол. Вы отключаете кнопку Maximize, удаляете команду Maximize из системного меню и блокируете любые другие элементы управления, которые позволили бы полностью развернуть окно. Поскольку вы параноик (а таково большинство хороших программистов), вы добавляете выражение ASSERT, чтобы быть уверенным в том, что ваш код работает так, как было задумано:

void CMyWnd::OnSize(UINT nType, int cx, int cy)
{
ASSERT(nType != SIZE_MAXIMIZE);
... // используем cx, cy
}

Команда тестировщиков прогоняет вашу программу 87 способами, ASSERT ни разу не срабатывает, и вы решаете, что можно спокойно компилировать окончательную версию. Но без _DEBUG выражение ASSERT(nType!=SIZE_MAXIMIZE) раскрывается в ((void)0), и неожиданно nType становится неиспользуемым параметром! Вот вам и окончательная компиляция. Вы не можете закомментировать nType из списка параметров, потому что он нужен для ASSERT. Поэтому в такой ситуации, где параметр используется только в ASSERT или другом _DEBUG-коде, лишь UNREFERENCED_PARAMETER сделает компилятор счастливым как при отладочной сборке, так и при окончательной. Уловили суть?

Прежде чем закруглиться, не могу не упомянуть, что индивидуальные предупреждения компилятора можно подавлять директивой pragma warning:

#pragma warning( disable : 4100 )

4100 - код ошибки для неиспользуемого параметра. Эта директива действует на оставшуюся часть файла или модуля. Чтобы повторно включить данное преду-преждение:

#pragma warning( default : 4100 )

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

Вы могли бы подавить предупреждения о неиспользуемых параметрах в рамках одной функции, окружив ее директивами pragma warning:

#pragma warning( push )
#pragma warning( disable : 4100 )
void SomeFunction(...)
{
}
#pragma warning( pop )

Конечно, это было бы слишком нудно для неиспользуемых параметров, но, вероятно, понадобится для предупреждений других видов. Разработчики библиотек постоянно используют #pragma warning для блокировки предупреждений, чтобы их код можно было без проблем компилировать с ключом /W4. В MFC полно таких pragma. Параметров у директив #pragma warning гораздо больше, чем я упомянул здесь. Проверьте их в документации.

Вопрос Я заметил, что в некоторых приложениях есть специальные команды, которые появляются в контекстном меню для кнопки таких приложений на панели задач. Например, WinAmp (популярный медиа проигрыватель) добавляет подменю "WinAmp" с командами, специфическими для WinAmp. Как мне добавить собственные команды для кнопки приложения на панели задач?

Жирар Осигян (Jirair Osygian)

Вопрос Я создала простое MFC SDI-приложение с формой, на которой отображается счетчик. Мне нужно запускать и останавливать счетчик щелчком правой кнопки мыши на значке приложения, свернутого в кнопку на панели задач. Функции запуска и остановки прекрасно работают как кнопки на моей форме, и мне удалось добавить аналогичные команды в системное меню. Но когда я выбираю их в системном меню, ничего не происходит. Как обрабатывать сообщения от модифицированного системного меню?

Моник Шарман (Monicque Sharman)

Ответ Я отвечу на оба вопроса одним махом. Ответ на вопрос Жирара прост: меню, которое видит пользователь, щелкнув правой кнопкой мыши свернутое в кнопку на панели задач приложение, идентично меню, отображаемому при щелчке значка приложения в левом верхнем углу строки заголовка или при нажатии Alt+Space. На рис. 1 показано, что я имею в виду. Это меню называется системным, и в нем есть команды вроде Restore, Minimize, Maximize и Close.

Рис. 1. Системное меню

Вызовите ::GetSystemMenu, чтобы получить системное меню, а затем модифицируйте его, добавив, удалив или изменив нужные элементы. Вы можете или изменив нужные элементы. Вы можете даже полностью блокировать системное меню, отключив стиль WS_SYSMENU в флагах создания своего окна или в виртуальной функции PreCreateWindow. Но что бы вы ни делали, системное меню - это и то меню, которое отображается при щелч-ке правой кнопкой мыши свернутого в кнопку приложения на панели задач.

А это подводит нас к вопросу Моник: если в системное меню добавляются собственные команды, как их обрабатывать в MFC? Если вы поступите как обычно - напишете где-то обработчик ON_COMMAND и добавите его в одну из своих карт сообщений (message maps), - то обнаружите, что этот обработчик никогда не вызывается. В чем дело?

А дело в том, что Windows и MFC обрабатывают системные команды иначе, чем команды обычного меню. Когда пользователь щелкает команду обычного меню или кнопку на форме, Windows посылает вашему основному окну сообщение WM_COMMAND. Если вы пользуетесь MFC, ее механизм диспетчеризации команд перехватывает это сообщение и направляет его через систему любому объекту в карте команд, у которого имеется обработчик ON_COMMAND для этой команды. (Подробнее о диспетчеризации команд в MFC см. мою статью "Meandering Through the Maze of MFC Message and Command Routing" в "MSJ" за июль 1995 г. по ссылке www.microsoft.com/msj/0795/dilascia/dilascia.aspx.)

Но системные команды не идут через WM_COMMAND. Они поступают через другое сообщение, которое называется - а как же еще? - WM_SYSCOMMAND. Это относится как к истинно системным командам типа SC_MINIMIZE или SC_CLOSE, так и к добавляемым вами. Для обслуживания команд системного меню вы должны явно обрабатывать WM_SYSCOMMAND и выполнять проверку на идентификаторы своих команд. Это требует добавления ON_WM_SYSCOMMAND в карту сообщений основного окна, причем функция-обработчик должна выглядеть примерно так:

CMainFrame::OnSysCommand(UINT nID, LPARAM lp)
{
if (nID==ID_MY_COMMAND) {
... // обрабатываем
return 0;
}
// Передаем базовому классу - это важно!
return CFrameWnd::OnSysCommand(nID, lp);
}

Если команда не ваша, не забудьте передать ее базовому классу, - обычно это CFrameWnd или CMDIFrameWnd. Иначе Windows не получит сообщение и вы нарушите работу встроенных команд.

Обработка WM_SYSCOMMAND в основной рамке действует нормально, но какая-то она неуклюжая. Зачем использовать специальный механизм для обработки команд только потому, что они приходят через системное меню? А что если вы захотите обрабатывать системную команду в каком-то другом объекте, например в документе? Одна распространенная команда, помещаемая в системное меню, - About (ID_APP_ABOUT), и большинство MFC-программ обрабатывают ID_APP_ABOUT в объекте "приложение":

void CMyApp::OnAppAbout()
{
static CAboutDialog dlg;
dlg.DoModal();
}

Одно из по-настоящему замечательных средств MFC - ее система диспетчеризации команд (command-routing system), которая позволяет объектам, отличным от окон, например CMyApp, обрабатывать команды меню. Многие программисты даже не задумываются, насколько это необычно. Если вы уже обрабатываете ID_APP_ABOUT в своем объекте "приложение", то зачем вам реализовать отдельный механизм для поддержки ID_APP_ABOUT в системном меню?

Более эффективный способ (и в большей мере соответствующий стилю MFC) обработки дополнительных системных команд - передача их через обычный механизм диспетчеризации команд. Тогда вы смогли бы обрабатывать системные команды стандартным для MFC образом - с помощью обработчиков ON_COMMAND. Вы даже смогли бы использовать ON_UPDATE_COMMAND_UI для обновления элементов своего системного меню, например для отключения какого-то элемента или отображения галочки рядом с элементом.

На рис. 2 показан написанный мной небольшой класс CSysCmdRouter, который превращает системные команды в обычные. Для его использования вы должны создать экземпляр CSysCmdRouter в своей основной рамке и вызвать его метод Init из OnCreate:

int CMainFrame::OnCreate(...)
{
// Добавляем элементы в системное меню
CMenu* pMenu = GetSystemMenu(FALSE);
pMenu>AppendMenu(..ID_MYCMD1..);
pMenu>AppendMenu(..ID_MYCMD2..);
// Направляем системные команды через MFC
m_sysCmdHook.Init(this);
return 0;
}

Вызвав CSysCmdRouter::Init, вы можете обрабатывать ID_MYCMD1 и ID_MYCMD2 стандартным способом, с помощью обработчиков ON_COMMAND для любого объекта в MFC-схеме распределения команд - представления (view), документа, рамки (frame), приложения или другой мишени, добавленной переопределением OnCmdMsg. CSysCmdRouter также позволяет обновлять системное меню, используя обработчики ON_UPDATE_COMMAND_UI. Единственный подвох - идентификаторы ваших команд не должны конфликтовать с идентификаторами любых других команд меню (если только они не представляют ту же команду) или встроенных системных команд, которые начинаются с SC_SIZE = 0xF000. Visual Studio .NET присваивает идентификаторы команд, начиная с 0x8000 = 32768, так что, если вы позволите Visual Studio самостоятельно назначать идентификаторы, то все будет в порядке при условии, что у вас не более 0xF000-0x8000 = 0x7000 команд. В десятичном счислении это 28 672 команды. Ну а если в вашем приложении более 28 000 команд, вам нужно сходить на консультацию к психиатру в области программирования.

Как работает CSysCmdRouter? Просто: он использует мой универсальный класс CSubclassWnd, который я описывал во многих колонках. CSubclassWnd позволяет создавать подклассы оконных объектов MFC, не наследуя от них. CSysCmdRouter наследует от CSubclassWnd и с его помощью создает подкласс основной рамки. Он перехватывает сообщения WM_SYSCOMMAND, посылаемые рамке. Если идентификатор команды укладывается в диапазон, выделенный системным командам (больше, чем SC_SIZE = 0xF000), CSysCmdRouter пересылает его в Windows; в ином случае он глотает WM_SYSCOMMAND и заменяет его на WM_COMMAND, после чего MFC следует обычным процедурам, вызывая ваши обработчики ON_COMMAND. Ловко, да?

А как насчет обработчиков ON_UPDATE_COMMAND_UI? Как CSysCmdRouter заставляет их срабатывать для команд системного меню? Элементарно. Перед выводом меню Windows посылает вашему основному окну сообщение WM_INITMENUPOPUP. Это ваш шанс обновить элементы меню - включить или отключить их, добавить галочки и т. д. MFC захватывает WM_INITMENUPOPUP в CFrameWnd::OnInitMenuPopup и выполняет все операции, связанные с обновлением UI. MFC создает объект CCmdUI для каждого элемента меню и передает его соответствующим обработчикам ON_UPDATE_COMMAND_UI в ваших картах сообщений. MFC-функция, отвечающая за эту работу, - CFrameWnd::OnInitMenuPopup, которая начинается так:

void CFrameWnd::OnInitMenuPopup(CMenu* pMenu,
UINT nIndex, BOOL bSysMenu)
{
    if (bSysMenu)
    return; // не поддерживает системное меню
     ...
}

MFC ничего не делает для инициализации системного меню. Это берет на себя CSysCmdRouter. Он перехватывает WM_INITMENUPOPUP и сбрасывает флаг bSysMenu, который является старшим словом в LPARAM:

if (msg==WM_INITMENUPOPUP) {
lp = LOWORD(lp); // (присваивает HIWORD = 0)
}

Теперь, когда MFC получает WM_INITMENUPOPUP, она считает, что меню обычное. Все это прекрасно ралив CWnd::WindowProc, или сравнивать HMENU, если вам ботает, пока идентификаторы ваших команд не конфликтуют с настоящими системными командами. Единственное, что вы теряете, переопределяя OnInitMenuPopup, - возможность отличать системное меню от меню в основном окне. Но нельзя же получить все и сразу! Вы всегда можете обрабатывать WM_INIT-MENUPOPUP, переопреденужно различать системное и обычное меню. Но на самом деле вас не должно волновать, откуда поступает команда.

Чтобы показать, как все это действует на практике, я написал тестовую программу TBMenu. На рис. 3 представлено меню, выводимое "правым щелчком" кнопки программы TBMenu на панели задач. Как видите, в нижней части меню есть две дополнительные команды. Исходный код CMainFrame в TBMenu приведен на рис. 4. В OnCreate добавляются команды, которые обслуживаются обработчиками ON_COMMAND и ON_UPDATE_COMMAND_UI в карте сообщений CMainFrame. TBMenu обрабатывает ID_APP_ABOUT внутри своего класса "приложение" (не показан). CSysCmdRouter превращает системные команды в обычные.

 

Удачи в программировании!


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=23057