重构MFC消息映射以包括完全限定的成员函数指针

时间:2018-07-16 13:32:49

标签: c++ regex mfc automated-refactoring clang-cl

我有一个代码库,其中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)

或者我们可以从BEGIN_MESSAGE_MAP()宏中使用ThisClass typedef:

    ON_COMMAND(CID_ButtonAction, &ThisClass::OnButtonAction)

到目前为止很好...唯一的问题是我在许多单独的文件中有数百个消息映射条目。有什么工具可以解决此问题?一些晦涩的Visual Studio魔术?还是可以在这里通过正则表达式使用替换?

2 个答案:

答案 0 :(得分:2)

最后,我想出了我从MinGW运行的sed命令:

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::/将行中的最后一个逗号替换为, &ThisClass::后的0+个空格

样本输入:

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命令以解决此问题(例如,检查该行上的最后一个逗号是否后跟&),但这对我来说已经足够了。

答案 1 :(得分:1)

该错误消息有点奇怪,我认为它与Visual Studio和CLANG之间的区别有关,只要处理源即可。

我需要的编译器是Visual Studio 2005,并且我正在使用MFC应用程序,因此Visual Studio 2005的MFC源代码非常方便。我快速浏览了使用相同解决方案的Visual Studio 2015,它的MFC头文件似乎很相似。因此,我将基于Visual Studio 2005 MFC。

位于afxmsg_.h中的ON_COMMAND()宏定义如下:

#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是基类,可以从中继承其他类,例如CWndCWinThread以及其他使用消息映射的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)

ThisClassBEGIN_MESSAGE_MAP()指令(例如BEGIN_MESSAGE_MAP(CFrameworkWnd, CWin))中指定的类的typedef,并由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底部附近有一些宏。 / p>

例如,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<>则可以通过方法接口发出编译器错误来捕获由于接口缺陷而导致的程序员错误。与预期的不同。