(CListBox*)GetDlgItem(IDC_LIST1)->AddString("abc");
这是为list box控件增加内容的一种常见写法,习以为常了,一直没注意到这里有什么古怪。有次手痒,把它改成了
dynamic_cast<CListBox*>(GetDlgItem(IDC_LIST1))->AddString("abc");
发现crash了,原因是dynamic_cast<CListBox*>(GetDlgItem(IDC_LIST1)返回值为空。简单搜索了一下,看到很多人有这个困惑,但并没有在网上找到现在的有说服力的解释,就自己查了一下MFC这部分代码。发现这行代码执行的原理大致是这样的:
CWnd::GetDlgItem会在一个Handle Map里查找,如果有对应的对象就返回,如果没有就新建一个CWnd的对象返回它的指针。指针指向的对象就是一个CWnd对象,根本就不存在CListBox对象,所以dynamic_cast<CListBox*>(GetDlgItem(IDC_LIST1))返回值为空就很正常了。
但另一个疑问就出现了:既然CListBox对象不存在,那强制转换成CListBox*类型并调用它的函数为什么能执行呢?
这就得了解类中对象与函数的关系了。面向对象的说法给人一种幻觉,好像一个对象就是一个全功能的物体,数据和函数都在这个对象里,如果对象不存在,那函数也就不存在了。其实对象只包括数据,类的函数是共用的,如果有虚函数的话,有个虚表指针,如果没有虚函数的话,对象里就只有数据了。调用类的成员函数实际上是,把对象的地址作为参数来调用成员函数而已。函数只管检查参数类型是否正确,并不管对象是否真实存在。把AddString的定义写成下面这样就容易理解了:
int AddString(CListBox* pListBox, LPCTSTR lpszItem);
而这个函数能否执行成功,就看函数里是否使用了子类的数据成员,如果只使用了父类的数据成员,那么是可以工作的。再看一下AddString函数的实现:
_AFXWIN_INLINE int CListBox::AddString(LPCTSTR lpszItem)
{
ASSERT(::IsWindow(m_hWnd));
return (int)::SendMessage(m_hWnd, LB_ADDSTRING, 0, (LPARAM)lpszItem);
}
发现只调用了m_hWnd,这是父类的一个成员变量,所以是可以工作的。MFC里大量用SendMessage来编写成员函数,而不是直接调用对应的函数,这种通用性应该也是考虑的因素之一了。