分类:
2008-10-13 16:39:42
C++ Q&A 专栏...
调用虚拟函数,持续化视图状态,POD 类型概念
原著:Paul DiLascia
翻译:
原代码下载: (234KB)
原文出处:
在 C++
中,无法从某个类的构造函数中调用派生的虚拟函数,因为虚表还没有完全建立。但是在C#中好像就可以,是这样吗?为什么会有这种差别呢?
typeof(Obj1)==typeof(Obj2) sizeof(Obj1)!= sizeof(Obj2)
因为对象之一是被部分构造。(不要忘了垃圾收集器是异步运行的。)通过将对象构造成最终类型,垃圾收集器能从其类型决定对象的大小。如果 C# 像
C++
那样进行部分构造,则垃圾收集器将需要更多的代码来决定部分构造对象的真实大小。这样将带来复杂性和性能下降,首先要解决这个问题很让人气馁,所以为了较快的垃圾收集利益,微软的家伙们决定像上面那样来实现
C#。有关这方面的讨论参见 Raymond Chen 的 blog:“”。
在
的专栏中,你展示了如何改变文件打开对话框的最新视图状态设置,但没有涉及到保存这个用户使用的最新视图设置。我遇到的问题是读取用户已有的打开文件对话框设置。我只找到直接读取列表框信息的方法,但当用户选择缩略图模式时,那样做不能得到正确的信息。对此你有没有解决办法?
有几个读者都在问文件打开对话框中的缩略图问题。在我中,我示范了如果向文件打开对话框中的 SHELLDLL_DefView
专用窗口发送 WM_COMMAND 消息以设置不同的视图模式——但你如何知道当前所处的模式是哪一个呢?你必须获取列表控件并调用 CListCtrl::GetView:
// in dialog class HWND hlc = ::FindWindowEx(m_hWnd, NULL, _T("SysListView32"), NULL); CListCtrl* plc = (CListCtrl*)CWnd::FromHandle(hlc); DWORD dwView = plc->GetView();
CListCtrl::GetView 返回 LV_XXX 代码之一,但正像 Maarten 发现的那样,Windows
对图标模式和缩略图模式都返回 LV_VIEW_ICON。
那么如何区分到底是哪种视图模式呢?我绞尽脑汁并钻进头文件查找,最后发现一个叫 LVM_GETITEMSPACING
的消息,该消息是作什么用的呢——用来获取图标间隔。顾名思义,图标间隔是图标视图模式中图标之间的像素间隔。LVM_GETITEMSPACING
不是很好使用,以至于 MFC 都没有对之进行包装(比如说 MFC 中并没有 CListCtrl::GetIconSpacing 这样的函数)。所以在
MFC 中你得自己发送消息:
CSize sz = CSize(plc->SendMessage(LVM_GETITEMSPACING));
Windows 按照通常方式返回尺寸,在高位和低位字中编码的 cx/cy,然后CSize很礼貌地为你进行解码。一旦有了图标间隔,你便可以将它与 GetSystemMetrics(SM_CXICONSPACING) 返回的系统间隔值进行比较。如果列表视图的图标间隔与系统的一样,则视图是图标模式。如果大于系统间隔,则视图为缩略图模式:
if (sz.cx > GetSystemMetrics(SM_CXICONSPACING)) { // thumbnail view } else { // icon view }
讲了那么多缩略图,接下来的问题是如何持续化不同用户会话的视图状态?对此,当程序终止时,你需要用 Profile 函数在用户配置文件中保存最后使用的模式,并在下一次启动程序时再次恢复它。我写了一个小示范程序,DlgTest。程序使用了一个实现持续化程序行为的类 CPersistOpenDlg。这个类又借助另外一个类 CListViewShellWnd,用它来封装 SHELLDLL_DefView 窗口(参见)。CListViewShellWnd 包含获取和设置视图模式的函数,由这些函数来区分图标和缩略图模式:
CListViewShellWnd m_wndLVSW; ... m_wndLVSW.SetViewMode(ODM_VIEW_THUMBS);
CListViewShellWnd 的 OnDestroy 处理器在某个数据成员 m_lastViewMode
中保存视图模式。当对话框被销毁时,CPersistOpenDlg 的析构函数调用 WriteProfileInt
将这个值写入用户配置文件。对话框启动时,CPersistOpenDlg 给自己送一个初始化消息;该消息处理例程调用 GetProfileInt
从磁盘读取存储在配置文件中的值并设置视图模式。PostMessage 是必须调用的,因为常规初始化消息 WM_INITDIALOG 和
CDN_INITDONE 在文件对话框被完全初始化之前就会到来——有关这一点的解释参见。
顺便说一下,任何时候你都应该使用 GetProfileXxx 和 WriteProfileXxx 来持续化应用程序的设置。MFC 用
CWinApp 包装了这些函数。如果你在应用程序启动时调用(一般都是在 InitInstance 函数中) CMyApp::SetRegistryKey("KeyName"),MFC
使用注册表来存储用户配置信息,而不是 INI 文件。下面是 DlgTest 用的 INI 文件:
[settings] ViewMode=28717
偶尔在一些文字资料和 C++ 文档以及 Microsoft .NET
框架中看到术语“POD 类型”。这个术语是什么意思?
struct RECT r; // value undefined POINT *ppoints = new POINT[100]; // ditto CString s; // calls ctor ==> not POD
非 POD 类型通常需要初始化,不论是调用缺省的构造函数(编译器提供的)还是自己写的构造函数。
过去, POD 对于编写编译器或与C 兼容的 C++ 程序的人来说很重要。现在,POD 来到 .NET 的环境中。在托管 C++
中,托管类型(包括 __value 和 __gc 两者)能包含嵌入的原生 POD 类型。
展示了例举说明代码。托管的 Circle
类能包含 POINT,但无法包含 CPoint 类。如果你尝试编译 pod.cpp 会报一个 C3633 错误:“Cannot define ''m_center'' as a
member of managed ''Circle'' because of the presence of default constructor ''CPoint::CPoint''
on class ''CPoint''.”(译者:意思是由于类 CPoint 有缺省的构造函数‘CPoint::CPoint’,所以不能将‘m_center’定义为托管类‘Circle’的一个成员)
.NET 限定嵌入的本地对象只能为 POD 类型的理由是这样做能安全地拷贝它们,不用担心调用构造函数,初始化虚表,或任何非 POD
类型需要的其它机制。
向 Paul 提问和评论请发到 cppqa@microsoft.com.