我有一个代码库,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魔术?或者可以在这里通过regex使用替换吗?
最后,我提出了一个从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::/
用, &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命令可以根据这一点进行调整(例如,检查行中最后一个逗号后面是否跟有&),但这对我来说已经足够了。
编辑-添加了-b
选项。这将文件视为二进制文件。在Windows上,这可以防止用Unix字符替换原始换行符-如果不启用此选项,任何处理过的文件的git diff都将看起来像是整个文件被删除并再次添加。
错误消息有点奇怪,我认为这与Visual Studio和CLANG在处理源代码方面的差异有关。
我手头的编译器是Visual Studio 2005,我有一个MFC应用程序,所以Visual Studio 2005的MFC源代码很方便。我用同样的解决方案快速浏览了一下Visual Studio 2015,发现MFC头文件似乎相似。所以我将以VisualStudio2005MFC为基础。
位于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);
类CCD_ 14是从其派生诸如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
是BEGIN_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[] =
{
我在VisualStudio中测试了这种方法,一切都编译得很好,它可以在VisualStudio2005中使用。
请注意,可能还有其他消息映射宏可能需要类似的解决方法,因为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)
合计
使用这种用自己的版本覆盖默认宏的方法,include文件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至少可以从VisualStudio6.x开始,而且有机会使宏统一。例如,VisualStudio2005的发布将是一个好时机。也许有人担心与巨大的VisualStudio6.x MFC代码库的向后兼容性?
现在我知道为什么要定制特定的static_cast<>
。它允许检测具有错误或不匹配接口签名的类方法以及编译错误。因此,C风格的转换是为了纠正AFX_MSGMAP_ENTRY
中函数指针的定义,而static_cast<>
是为了在方法接口与预期接口不同的情况下,通过发出编译器错误来捕捉由于接口缺陷而导致的程序员错误。