Рефакторинг карт сообщений 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)

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

    ON_COMMAND(CID_ButtonAction, &ThisClass::OnButtonAction)

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


person pablo285    schedule 16.07.2018    source источник
comment
Единственным поддерживаемым компилятором для компиляции кода MFC является компилятор Microsoft, который поставляется с Visual Studio, соответствующей вашей версии MFC. Другие компиляторы не поддерживаются. Даже если вы решите эту проблему, в будущем будет еще много проблем, которые нужно исправить. И если что-то пойдет не так, вы сами по себе. Нет поддержки от Microsoft.   -  person IInspectable    schedule 16.07.2018
comment
Моя мотивация здесь состоит в том, чтобы иметь возможность использовать clang-tidy через плагин Clang Power Tools для VS, и мне нужен код для компиляции для этого - больше ничего. Я не буду использовать бинарные файлы, скомпилированные clang, в производстве, поэтому нет необходимости в поддержке или риске. Кроме того, это исправление действительно улучшит качество кода, сделав его более совместимым со стандартами.   -  person pablo285    schedule 16.07.2018
comment
Тогда вам следует спросить, как использовать clang-tidy с MFC. Этот вопрос спрашивает, как скомпилировать код MFC с помощью Clang. Это разные вопросы, и на них могут быть разные ответы.   -  person IInspectable    schedule 16.07.2018
comment
Я знаю обходной путь для этого — переопределить все макросы MFC до нуля, когда определен clang, но я не хочу идти по этому пути. Хотя использование clang-tidy с кодом MFC является основной мотивацией, я также хочу иметь возможность компилировать и связывать в clang, если это возможно, и это серьезное препятствие на моем пути туда.   -  person pablo285    schedule 16.07.2018
comment
Итак, вы пытаетесь скомпилировать MFC с помощью Clang. То есть просто не поддерживается, и я даже не уверен, что это законно. Вам придется копаться в лицензионном соглашении, чтобы проверить.   -  person IInspectable    schedule 16.07.2018
comment
Да. Я. Вы только что доказали то, что я сказал в исходном вопросе. Как это конструктивно? Eula не накладывает никаких ограничений на компиляцию распространяемого кода, такого как MFC. Кажется, я ясно и кратко изложил свой вопрос, можем ли мы вернуться к нему?   -  person pablo285    schedule 16.07.2018
comment
Э, хорошо. Просто пытаюсь понять проблему... Visual Studio не предоставляет нужную вам функцию, во всяком случае, я бы не знал. Я не знаю никаких инструментов, которые делают то, что вы хотите. Это не значит, что их нет. Я также не знаю регулярного выражения, которое может анализировать С++. Однако, учитывая очень ограниченную проблему, вы должны быть в состоянии придумать регулярное выражение для запуска вашего кода, чтобы преобразовать его, чтобы он стал совместимым с Clang. Как ни странно, это, пожалуй, наименее конструктивный комментарий по данному вопросу.   -  person IInspectable    schedule 16.07.2018
comment
Пожалуйста, включите фактический текст ошибки в свой опубликованный вопрос.   -  person Richard Chambers    schedule 16.07.2018


Ответы (2)


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

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

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

  • -b рассматривать файлы как двоичные (необязательно, чтобы сохранить окончания строк в Windows)*
  • -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 можно было скорректировать с учетом этого (например, проверить, не следует ли за последней запятой в строке &), но этого было достаточно для моих целей.

ИЗМЕНИТЬ — добавлена ​​опция -b. Это обрабатывает файлы как двоичные. В Windows это предотвращает замену исходных символов новой строки символами Unix - без включения этой опции git diff для любого обработанного файла будет выглядеть так, как будто весь файл был удален и добавлен снова.

person pablo285    schedule 18.07.2018

Сообщение об ошибке немного странное, и я полагаю, что оно связано с разницей между 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<> — отлавливать ошибки программиста из-за дефектного интерфейса, выдавая ошибку компилятора, если интерфейс метода отличается от ожидаемого.

person Richard Chambers    schedule 16.07.2018
comment
Это сработает для Clang. Проблема здесь в том, что мои карты сообщений используют не только ON_COMMAND, но и еще около 30 макросов, которые мне пришлось бы переписать таким образом. Компиляция Clang будет зависеть от модифицированного afxmsg_.h, который имеет несколько недостатков — переносимость на другие машины и неработающую компиляцию при каждом обновлении MFC. Сами Microsoft рекомендуют использовать полные имена функций-членов в макросах карты сообщений, поэтому я попытаюсь придумать регулярное выражение, чтобы правильно это исправить. Хотя ответ хороший, спасибо. - person pablo285; 17.07.2018
comment
@ pablo285 pablo285 Я почти уверен, что Microsoft не собирается делать обновление MFC в этой области, которое сломало бы десятилетия исходного кода, восходящего к 1990-м годам. У меня есть код MFC, первоначально написанный с помощью Visual Studio 6.x, который отлично компилируется с Visual Studio 2017, поэтому они, безусловно, прилагают усилия для обратной совместимости в областях, которые не связаны со стандартами C++. Интересно, разрешил ли VS 6.X указатель на метод класса, поэтому Microsoft с тех пор должна была разрешать его? Существуют параметры компилятора /Za и /Zc Visual Studio C++, которые изменяют поведение компилятора VS. - person Richard Chambers; 17.07.2018