Рефакторинг карт сообщений MFC для включения полностью определенных указателей на функции-члены

У меня есть кодовая база, где карты сообщений MFC написаны в этой форме:

BEGIN_MESSAGE_MAP(SomeForm, BaseForm)
ON_COMMAND(CID_ButtonAction, OnButtonAction)
END_MESSAGE_MAP()

Это прекрасно компилируется в MSVC. Когда я хочу скомпилировать тот же код в Clang, я получаю call to non-static member function without an object argument ошибка, потому что OnButtonAction не является правильной формой для указания указателя на функцию-член. Код можно легко исправить:

    ON_COMMAND(CID_ButtonAction, &SomeForm::OnButtonAction)

или мы можем использовать thisClass typedef из макроса BEGIN_MESSAGE_MAP ():

    ON_COMMAND(CID_ButtonAction, &ThisClass::OnButtonAction)

Пока все хорошо … единственная проблема состоит в том, что у меня есть сотни этих записей карты сообщений во множестве отдельных файлов. Есть ли инструмент, который может это исправить? Некоторая неясная магия Visual Studio? Или здесь можно использовать замену через регулярное выражение?

2

Решение

В конце концов я получил команду sed, которую я запускал из MinGW:

sed -i -re '/^BEGIN_MESSAGE_MAP/,/^END_MESSAGE_MAP/{/(BEGIN_MESSAGE_MAP|\/\/)/!s/(.*),\s{0,}/\1, \&ThisClass::/;}' *.cpp

Чтобы объяснить, что он делает:

  • -re поддержка расширенных регулярных выражений
  • -i замена на месте
  • /^BEGIN_MESSAGE_MAP/,/^END_MESSAGE_MAP/ сопоставлять только текст между этими двумя строками
  • /!s команда подстановки, которая будет игнорировать все, что вы соответствуете до него
  • /\(BEGIN_MESSAGE_MAP\|\/\/\)/ сопоставляет начала строк, которые нужно игнорировать (либо первую строку карты сообщений, либо закомментированные строки)
  • /(.*),\s{0,}/\1, \&ThisClass::/ заменяет последнюю запятую в строке за 0+ пробелами на , &ThisClass::

Пример ввода:

BEGIN_MESSAGE_MAP(SomeForm, BaseForm)
ON_COMMAND(CID_ButtonAction, OnButtonAction)
ON_NOTIFY_EX(CID_Notify, 0, OnNotify)
END_MESSAGE_MAP()

Выход:

BEGIN_MESSAGE_MAP(SomeForm, BaseForm)
ON_COMMAND(CID_ButtonAction, &ThisClass::OnButtonAction)
ON_NOTIFY_EX(CID_Notify, 0, &ThisClass::OnNotify)
END_MESSAGE_MAP()

Это работало хорошо, для ~ 500 файлов мне нужно было всего лишь сделать две ручные настройки, когда уже использовалась нотация членства в методе класса. Команда sed может быть скорректирована для учета этого (например, проверить, если после последней запятой в строке следуют &) но этого было достаточно для моих целей.

2

Другие решения

Сообщение об ошибке немного странное, и я полагаю, что оно связано с разницей между Visual Studio и CLANG в части обработки исходного кода.

Компилятор, который у меня есть, это Visual Studio 2005, и у меня есть приложение MFC, над которым я работаю, поэтому удобен исходный код MFC для Visual Studio 2005. Я быстро взглянул на Visual Studio 2015 с тем же решением, и оказалось, что заголовочные файлы MFC похожи. Поэтому я собираюсь основывать это на Visual Studio 2005 MFC.

ON_COMMAND() макрос, расположенный в afxmsg_.h, определяется следующим образом:

#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
static_cast<AFX_PMSG> (memberFxn) },
// ON_COMMAND(id, OnBar) is the same as
//   ON_CONTROL(0, id, OnBar) or ON_BN_CLICKED(0, id, OnBar)

А также AFX_PMSG определяется в файле afxwin.h как:

// pointer to afx_msg member function
#ifndef AFX_MSG_CALL
#define AFX_MSG_CALL
#endif
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

Класс CCmdTarget является базовым классом, из которого получены другие классы, такие как CWnd а также CWinThreadи другие классы MFC, которые используют карту сообщений.

Итак ON_COMMAND() макрос использует static_cast<> какой должен быть базовый класс окна или целевой поток. Возможно, кто-то еще, более осведомленный, может дать реальное объяснение того, что делает компилятор и как спецификация языка C ++ будет относиться к этой конструкции.

Однако, с практической точки зрения, я предлагаю вам написать свою собственную версию ON_COMMAND() макрос и вставьте эту версию в файл StdAfx.h, который находится в каждом проекте вашего решения. Я выбрал файл StdAfx.h, так как на проект есть только один файл, и это центральная точка, в которой одна модификация может повлиять на несколько модулей компиляции.

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

#undef ON_COMMAND

#define ON_COMMAND(id, memberFxn) \
{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
static_cast<AFX_PMSG> (&ThisClass :: memberFxn) },
// ON_COMMAND(id, OnBar) is the same as
//   ON_CONTROL(0, id, OnBar) or ON_BN_CLICKED(0, id, OnBar)

Это делает две вещи.

Прежде всего, это не определяет текущее определение ON_COMMAND() макрос, так что вы можете заменить его своим.

Во-вторых, он использует нотацию членства метода класса для указателя метода. Я не могу протестировать с CLANG, однако он должен выполнить ту же замену исходного текста, что вы делали вручную, что, как вы говорите, работает.

ON_COMMAND(CID_ButtonAction, &SomeForm::OnButtonAction)

ThisClass является typedef для класса, указанного в BEGIN_MESSAGE_MAP() директива (например, BEGIN_MESSAGE_MAP(CFrameworkWnd, CWin)) и генерируется BEGIN_MESSAGE_MAP() макрос, который выглядит так:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass;                        \
typedef baseClass TheBaseClass;                    \
static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
{

Я протестировал этот подход с Visual Studio, и все прекрасно компилируется, и он работает с Visual Studio 2005.

Обратите внимание, что могут существовать другие макросы карты сообщений, которые могут потребовать аналогичного обходного пути, что и использование static_cast<AFX_PMSG> кажется довольно распространенным в большинстве макросов карты сообщений.

Любопытная разница

Глядя на это, есть одно любопытное различие в различных макросах в afxmsg_.h — это полный набор макросов, которые используют нотацию указателя метода класса. Примером является следующее:

#define ON_WM_PAINT() \
{ WM_PAINT, 0, 0, 0, AfxSig_vv, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnPaint)) },

Глядя на некоторые конкретные макросы событий, кажется, что они повторно используют ON_CONTROL() макрос, так что заменить этот макрос в дополнение к ON_COMMAND() макрос будет распространяться через набор макросов управления MFC.

// Combo Box Notification Codes
#define ON_CBN_ERRSPACE(id, memberFxn) \
ON_CONTROL(CBN_ERRSPACE, id, memberFxn)

Суммирование

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

Например, ON_MESSAGE() макрос потребуется изменить на:

// for Windows messages
#define ON_MESSAGE(message, memberFxn) \
{ message, 0, 0, 0, AfxSig_lwl, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \
(&ThisClass :: memberFxn)) },

Интересно, почему существует сочетание стилей (возможно, из-за того, что разные люди добавляли новые макросы на протяжении многих лет и не удосужились изменить существующие?). Мне любопытно, почему это не решалось в течение последних двух десятилетий, поскольку MFC датируется как минимум Visual Studio 6.x, и были бы возможности сделать макросы единообразными. Например, выпуск Visual Studio 2005 был бы хорошим временем. Возможно, была проблема обратной совместимости с этой огромной кодовой базой Visual Studio 6.x MFC?

И теперь я знаю, почему специально static_cast<>, Это позволяет обнаруживать метод класса с неправильной или несовпадающей сигнатурой интерфейса с ошибкой компиляции. Таким образом, приведение в стиле C должно привести к правильному определению указателя на функцию в AFX_MSGMAP_ENTRY и static_cast<> заключается в обнаружении ошибок программиста из-за неисправного интерфейса путем выдачи ошибки компилятора, если интерфейс метода отличается от ожидаемого.

1