4

我有一个代码库,其中 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 魔法?还是可以在这里通过正则表达式使用替换?

4

2 回答 2

3

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

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 看起来就像整个文件已被删除并再次添加。

于 2018-07-18T12:39:32.687 回答
2

错误消息有点奇怪,我想它与 Visual Studio 和 CLANG 之间在处理源代码方面的差异有关。

我使用的编译器是 Visual Studio 2005,我正在开发一个 MFC 应用程序,因此 Visual Studio 2005 的 MFC 源代码很方便。我用相同的解决方案快速浏览了 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是一个基类,从它派生出其他类,例如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)),由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<>如果方法接口与预期的不同,则通过发出编译器错误来捕获由于接口缺陷导致的程序员错误。

于 2018-07-16T21:49:40.630 回答