星期三, 十一月 08, 2006

My blog was migrated...

Because of blockage, the readers from china were unable to read these posts. So I decide to migrate my blog, which is written in chinse, to CSDN.net. Here is the homepage of my new blog: http://blog.csdn.net/xushiweizh. And late I will write something in english to http://codeproject.com.

转移战场...

由于blogspot被盾,不得不考虑转移战场了...

原先winxcn.blogspot.com主要访问来源有二:搜索引擎和一些一直关注WINX的朋友。目前只剩下几个骨灰级的朋友,和从sourceforge的链接(http://winxcn.com)无意中过来一些老外。

决定中文blog主要在CSDN上维护,网址:http://blog.csdn.net/xushiweizh
下阶段可能会增加英文文档,目前考虑转战CodeProject。

星期一, 十一月 06, 2006

应用程序框架设计(2):SW系统的窗口类

解释了消息分派机制后,接下来我们开始介绍WINX的窗口类。为了产生比较的效果,我决定从之前我写的“SW系统”的窗口类讲起。在你理解了SW系统的窗口类后,我们再来看6年后WINX中的窗口类在设计上发生了什么样的变化。——这自然也是我个人在窗口类观念上的改变。

1、SW系统的“Hello,World!”程序

#define Uses_SApp
#include <sw.h>
// SW系统中,你需要记住头文件只有,它是SW系统总控文件。
// 你只需要告诉它,你用了什么,它可以为你检测需要的头文件并包含它们。
// 例如,这里我们用了SApp类,故有 #define Uses_SApp 一句。

class SHelloApp : public SApp
{
public:
 void OnDraw(SHDC dc);
};

void SHelloApp::OnDraw(SHDC dc)
{
 dc.TextOut(1, 1, _T(“Hello, World!”));
}

// SW系统没有封装WinMain函数,这应该是一个好消息吧?
// 你不必对主函数是main()而非是WinMain太过关心。它只是为了与DOS
// 习惯兼容以及书写的方便而实现的宏而已。
int main()
{
 return Desktop.Execute(new SHelloApp);
 // Desktop就是Windows系统桌面,它是一个特殊的窗口对象。
 // 特别注意,SW系统中App类是一个窗口!
}

2、SW系统的类体系图


类说明:
 SHWnd:对Windows窗口句柄的简单包装
 SObject:类库的根
 SString:字符串类
 SArchive:文档类,用于存盘
 SHxxxCtrl:各种控件
 SWnd:窗口类基本类
 SHDC:对DC句柄的简单包装
 SMsg:消息类
 SSubclass:派生子类
 SDlg:对话框类
 SApp:应用程序类(SDI)
 SMDIChild:MDI程序文档窗口
 SMDIApp:应用程序类(MDI)

注:SW系统中对话框不分模态/非模态,只有在调用时才有区别。

如果希望是模态的:
 hWndParent.ExecDlg(new SxxxDlg); // 调用DialogBoxParam
或:
 hWndParent.Execute(new SxxxDlg, SW_SHOW);
  // 使用SW系统的消息循环

如果希望是非模态的:
 hWndParent.Insert(new SxxxDlg);
  // 像普通窗口一样插入到父窗口中即可

3、独特的窗口(视图)模型

注: 请注意术语上的变化,由于SW系统在Windows下实现,使用了Windows中的一些术语。在这里视图被称为窗口,事件被称为消息。

窗口应该具备哪些行为才合理?象显示/隐藏、选择(激活)、移动、改变大小、关闭等这些基本行为是很容易想到的。SW系统与经典应用程序框架如MFC不同的是,它引入了另两个非常有用的方法:

1)Insert操作:

SW系统中,窗口都通过Insert操作插入到父窗口中。

2)Execute操作:

SW系统中,可以在一个窗口A中运行(Execute)另一个窗口B。此时,窗口A作为窗口B的父窗口,已经不再接受消息,即窗口B是模态视图。不过,在窗口A是Desktop(这里Desktop是Windows系统桌面)时稍微有点不同。这是由于我们使用得是多任务系统所致。

在SW系统中,应用程序是一个窗口,由SWnd类派生;这不同于MFC,在那里应用程序是一个线程,由CWinThread类派生。这一点也许MFC的设计可能严谨一些,但是SW系统的做法的优点是,程序的逻辑变得相当简单。SW系统如何运行程序?非常清楚,只要一句话:
  Desktop.Execute(new SxxxApp);
这个我们在例子程序中已经看到了。

Execute操作很复杂吗?打开SW系统的源代码,你可能很失望,代码只有几句话:

UINT SHWnd::Execute(SWnd *pWnd, int nCmdShow)
{
 if (this->Insert(pWnd))
 {
  pWnd->Show(nCmdShow);
  return pWnd->MsgLoop();
 }
 return –1;
}

很显然,Desktop.Execute(new SxxxApp)的含义是:

1)将应用程序插入(Insert)到Desktop;
2)显示应用程序;
3)进入消息循环;

Insert操作又做了什么?它的代码就相对复杂,且与Windows系统密切相关,这里不具体写出它的代码。它完成的工作主要是:

1)检测窗口类是否已经注册;如果没有,注册窗口类;
2)调用窗口创建函数创建窗口对象;
3)由事件发送器将WM_CREATE发送给窗口对象;

消息循环(MsgLoop)的实现流程我们一开始就已经给出,即“事件驱动”应用中的消息流动过程。我们也提到其中事件发送器Windows只实现了一部分。现在问题的关键在于,如何将消息由窗口过程发送给具体的窗口对象?

MFC采用一张Hash表将窗口句柄与窗口实例指针关联起来。在这一点上SW系统采取了一个取巧的办法,避免去维护一张Hash表。它利用窗口句柄的USERDATA字节保存对象指针。具体代码如下:

// 为了突出问题的关键,本文的代码一般比较简略,省略了没有大多数的出错
// 处理。作为实际的程序,其风格当然不应该这样。
LONG CALLBACK WndProc(
 HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 SWnd* pWnd = (SWnd*)GetWindowLong(hwnd, GWL_USERDATA);
 if (pWnd)
 {
  SMsg ev;
  ev.uMsg = uMsg;
  ev.lParam = lParam;
  ev.wParam = wParam;
  return pWnd->HandleMsg(ev);
 }
 if (uMsg == WM_CREATE)
 {
  pWnd = (SWnd*)((CREATESTRUCT*)lParam)->lpCreateParams;
  // 这是从CreateWindow函数传进来的窗口实例指针!
  pWnd->m_hWnd = hwnd;
  SetWindowLong(hwnd, GWL_USERDATA, (LONG)pWnd);
  return pWnd->OnCreate() ? 0 : -1;
 }
 return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}

在这个技巧中存在的问题是,所有在WM_CREATE之前的消息被“遗失”了。解决这个限制的方案是简单的。核心思想是用全局变量传递窗口实例指针,而不是在CreateWindow函数的参数中传递。见下:

创建窗口代码:
 LOCK();
 x_pThisWnd = 窗口实例指针;
 调用CreateWindow创建窗口;
 UNLOCK();

窗口过程代码:
 LONG CALLBACK WndProc(
   HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
  SWnd* pWnd = (SWnd*)GetWindowLong(hwnd, GWL_USERDATA);
  if (!pWnd)
  {
   pWnd = x_pThisWnd;
   pWnd->m_hWnd = hwnd;
   SetWindowLong(hwnd, GWL_USERDATA, (LONG)pWnd);
  }
  SMsg ev;
  ev.uMsg = uMsg;
  ev.lParam = lParam;
  ev.wParam = wParam;
  return pWnd->HandleMsg(ev);
 }

这里LOCK()/UNLOCK()是多线程互斥代码。由于使用了全局变量,线程互斥是必要的。

可以看到,所有的消息都是经由HandleMsg处理的。这是一个进步。但是这中间还有一个不妥当的地方。事实上我们已经习惯于在WM_CREATE消息中对窗口实例进行初始化。因此在WM_CREATE之前的消息处理时,对象其实还没有初始化完毕。这一点如果被遗忘就会犯错。而且,从概念上说,初始化总应该是对象接收到第一个消息。正是由于这一点,SW系统决定仍然使用原先的方案。

不小心上了CSDN首页...

呵呵,我申请csdn的blog才一个月哩。

星期日, 十一月 05, 2006

应用程序框架设计(1):SW系统简介

《应用程序框架设计》是我大学毕业时(2000年)写的毕业论文。在我给公司内部作“应用程序架构”方面的讲座时,曾经作为入门级的参考资料附上。后来不知如何就流传到Internet上,不过是不完整的版本(可尝试在Google中搜索“应用程序框架设计:SW系统”)。回头看这篇文字,最大的感受觉得自己的文字功底是越来越退步了:-) 由于与界面库有关,大家不妨看看。


应用程序框架设计

许式伟
2000年6月


一、摘要

随着面向对象技术的发展成熟,已经出现了许多著名的应用程序框架,如在Windows平台下有MFC、VCL、OWL等;在旧的DOS系统下有Turbo Vision。在这里我希望通过我设计的“SW系统”来阐述我对设计应用程序框架一些想法。其中涉及的内容主要有:

  • 应用程序框架设计的基本内容

    这一部分主要是讨论应用程序框架的必要性、可行性,以及设计应用程序框架的基本思路。
     
  • SW系统的总体内容与实现

    这一部分主要讨论作为一个应用程序框架,SW系统的总体结构和内容,并对SW系统一些重要的实现细节做出说明。其中主要有:SW系统中的窗口模型、属性、SW系统的RuntimeClass支持和序列化等。在最后,我们要分析经典应用程序框架的缺陷。同时也说明由SW系统向COM转变的必然
     
  • SW系统的新方向:基于COM(组件)思想的应用程序框架

    这一部分主要介绍组件思想的基本内容。SW系统的对组件思想的实现。

二、应用程序框架设计的基本内容

一个应用程序到底有多少“骨头”,多少“肉”?这里所说的“肉”是指程序中用于解决问题的那一部分,而“骨头”是指“肉”所依附的程序框架部分,它们是为了实现与用户交互、使界面友好必需做的事情。

对于解决问题的逻辑,我们很难能够找到一个一般做法来简化这件事。只有在具体定位到某一具体的方向时,才有可能做到这一点。例如你要进行数值计算,可能会需要一个功能完善的数学包;你要进行图象处理,可能需要一个图象处理库;等等。严格的说,这些东西说不上是一个框架,只是一个个工具包(Utilities)。因为它们一般没有复杂的调用规则,函数之间相当独立。

对于应用程序与用户的交互,在DOS时代编程的人一定对此感触很深。DOS时期基本上程序与用户交互的动作都是自己完成的。这样做的结果往往不是觉得在界面设计上浪费了太多时间,就是觉得界面设计得不尽人意。各个程序的框架代码有大量的反复,但由于编程方法的局限,程序代码的重用效率往往比较低。

面向对象思想的成熟促使了种种应用程序框架的诞生。面向对象语言中,类的继存可以完成对大量现成代码重用的重用;动态束定(即虚函数机制)技术有效地将具体的实现代码延迟到设计阶段。而一种称为“事件驱动模式”的应用程序结构使程序的框架代码与实现细节彻底发生了分离。尽管现在的应用程序框架种类挺多,但它们在实现思想上相当一致:所有的这些应用程序框架都是“事件驱动模式”的一个应用。这也包括本文要介绍的SW系统。

“事件驱动”的核心自然是事件。从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。事件发送器负责将收集器收集到的事件分发到目标对象中。事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定,因而需要运用虚函数机制(函数名往往取为类似于HandleMsg的一个名字)。对于框架的使用者来说,他们唯一能够看到的是事件处理器。这也是他们所关心的内容。

视图(即我们通常所说的“窗口”)是“事件驱动”应用程序的另一个要元。它是我们所说的事件发送器的目标对象。视图接受事件并能够对其进行处理。当我们将事件发送到具体的视图时,实际上我们完成了一个根本性的变化:从传统的流线型程序结构到事件触发方式的转变。这样应用程序具备相当的柔性,可以应付种种离散的、随机的事件。

由于Windows本身是基于“事件驱动”模型的。因而在Windows操作系统下实现应用程序框架有相当的便利。在事件驱动程序的基本单元中,事件收集器已经由Windows系统完成;事件发送器也已经由Windows完成了部分内容。之所以是部分而非完全是因为Windows是用C语言实现的,而不是C++。由于没有对象,Windows将事件发送到所谓的“窗口函数”中(尽管不是发送到具体的对象,但应该说这是面向对象方式实现的一个变体)。要感谢Windows做了这件事。确定事件的目标所要做的工作的复杂可能要超出我们的想象。我们要对此进行定性的讨论。

根据事件的发送路线,事件可以分为以下几种:

  • 位置事件(鼠标事件)

    它的目标比较明确,即包含鼠标所在点的视图。不过这也有不适用的时候。在拖动时,往往希望鼠标消息的接收者是鼠标刚开始拖动时的视图,而非真正包含鼠标点位置的那个视图。正是考虑到这一点,Windows提出了“捕获鼠标消息”的概念。

  • 焦点事件(键盘事件、命令事件等)

    对于焦点事件消息,情形就比较复杂。为了说明白这一点,需要引入模态视图、焦点视图等概念。

    一个应用程序的视图结构通常由一个树型结构维护。在某个时刻,总存在一个视图,它的所有祖先视图不再接受事件,而它及其所有子视图仍然处理事件。这个视图被称为模态视图。它称得上是“活动着的根视图”。模态视图的一个典型例子是对话框。在它运行时是应用程序的其他部分都失去了响应。

    什么是焦点视图?模态视图首先是一个焦点视图。模态视图的子视图中,有一个视图是被激活的(或者称,它获得了“焦点”),它也是一个焦点视图。如果它也是复合视图(存在子视图的视图),它也可以有一个子焦点视图。这个逻辑一直到一个被激活的简单视图(对于它,还有一个名称,叫“热点视图”)。“热点视图”的所有父视图、父视图的父视图等等,直到模态视图,构成一个“焦点视图”链。

    总的说来,焦点事件首先发送给获当前的模态视图。在它不知道如何处理时,可能将事件顺着焦点视图链往下传。很多人在编程时往往发现某个事件处理函数总是不能够被执行。这种情况总是发生在焦点事件。其原因是由于祖先视图已经截获了该事件。如果能够对事件传递的过程理解了,应该怎样改写代码也就明确了。

    然而在焦点事件上有太多例外。有些视图没有获得焦点是也希望能够处理键盘消息。典型例子是菜单,对话框中的一些按钮等。针对这些Windows中已经有一些操作惯例,同时Windows也是不可能完全确定目标视图的。
     
  • 广播事件

    广播事件并不知道自己的目标,它的处理方式很简单,它将事件发送给所有视图。Windows并不支持广播消息。SW系统实现广播消息主要是为了逻辑的简化,例如关闭所有窗口、保存所有文件等,应用广播的概念是方便的。

WINX的消息分派机制(终结篇)

你已经了解了WINX的消息分派,这里我们总结一下,并交代一些前文为了思路紧凑而略过的一些细节,内容包括:

  • WINX消息分派的总体特色。
  • 与MFC、WTL相比它有什么优点与劣势。
  • Default函数是如何实现的。

开发WINX的时候,尽管我决定尽量重用WTL,以便这个界面库不至于和Sourceforge上其他众多的界面库一样,最后只是一个实验品(它们无法流行的原因多数在于体系封闭而个人精力有限而无法提供与当前流行的界面库同等的功能),只是拥有少量的忠实拥护者,但是最终无法成为工业级的产品(在我看来,C++界面库比较成功的有MFC、QT、wxWidgets,而WTL只能算半个)。但是,消息分派机制上我决定不沿袭WTL,而是自己提供全新的实现。

WTL的消息机制是极其灵活的,通过它你很容易将功能划分为一个个独立而且含义完整的功能切片。但是问题在于,这种灵活给用户带来了困惑:深奥的模板技术、复杂的消息机制、晦涩而丑陋的代码,用户望而却步了。

WINX的消息机制给用户最简洁的界面,并尽量与MFC的消息兼容。另一方面,WINX的消息分派的智能带来了另一个好处:WINX不断可以增加新的消息,只要派生类没用响应它,就没有任何额外开销。进而,我们可以为WINX加入任何新特性,只要用户没用使用该特性,那么就没用额外代价。——这很有趣。你马上可以想到,WTL的一个个功能切片,WINX也可以提供,并且可以以更为简洁的方式提供。

WINX的消息分派是高效的。一部分原因你已经了解到了:WINX它可以智能的了解派生类并做出优化。还有一个细节,同样与性能有关:WINX的消息响应次序在DispatchMessage中已经精心安排好了。你可以想象,把WM_PAINT消息放到switch..case的最后,还是放在最前,这对消息函数的执行效率产生了怎样的影响。而WTL中是由你自己去安排消息响应的次序,这对使用者是一个额外的负担。——这不只是因为性能的因素,WTL中某些功能切片的安放是有次序要求的,交换次序后会有细节上的行为差异。

WINX的消息机制最大的问题在于,由于我们随时可能会为了实现一个作用于所有窗口的新特性而添加新消息,故此,为了方便你未来升级WINX到更高的版本,你派生的窗口类需要小心定义其成员函数名。我的一个建议是,请尽量不要自定义一些以On开头的函数名。如果确实需要,建议引入类似命名空间的法则:例如,所有响应命令的函数统一以OnCmdXXX命名之。

MFC在你响应消息中希望执行默认处理时,除了调用基类的同名方法外,更为常见的方法是调用Default()函数。同样,WINX也提供了该函数,并且实现方式类似,大体代码如下:

__declspec(thread) PackedMessage _g_currMsg;

template <class T>
class WindowMessage
{
public:
 LRESULT Default()
 {
  T* pThis = static_cast<T*>(this);
  return pThis->InternalDefault(
   _g_currMsg.hWnd, _g_currMsg.message,
   _g_currMsg.wParam, _g_currMsg.lParam);
 }

 LRESULT ProcessMessage(
   HWND hWnd, UINT message ,
   WPARAM wParam, LPARAM lParam)
 {
  LRESULT lResult = 0;
  PackedMessage oldMsg = _g_currMsg;
  _g_currMsg.hWnd = hWnd;
  _g_currMsg.message = message;
  _g_currMsg.wParam = wParam;
  _g_currMsg.lParam = lParam;
  BOOL fProcess = DispatchMessage(
    hWnd, message, wParam, lParam, lResult);
  if (!fProcess)
   lResult = Defalut();
  _g_currMsg = oldMsg;
  return lResult;
 }
};

WINX的消息分派机制(续2)

我们继续Inside WINX's Message Dispatch。现在开始我们进入了最为关键的部分——WINX是怎么进行消息分派的。

从原理上来讲,WINX的消息分派函数(DispatchMessage)其实与上一篇:《WINX的消息分派机制(续)》中的并无多大的不同,只不过更加智能而已。其中最为关键的是,WINX引入了一种技巧,它可以在编译期判断一个函数是否被重载。简单来说,WINX的消息分派伪代码如下:

template <class T>
class WindowMessage
{
 ...

 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  T* pThis = static_cast<T*>(this);
  if (派生类重载了OnPaint && message == WM_PAINT)
    pThis->OnPaint(hWnd);
  else if (派生类重载了OnKeyDown && message == WM_KEYDOWN)
    pThis->OnKeyDown(hWnd, wParam, lParam);
  else if (...)
    ...
  else
    return FALSE;
  return TRUE;
 }
};

简单看一个实际的例子,这样做的好处就很明了了。设想WindowMessage的派生类只重载了OnPaint,那么WindowMessage类看起来是这样的:

template <class T>
class WindowMessage
{
 ...

 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  T* pThis = static_cast<T*>(this);
  if (true && message == WM_PAINT)
    pThis->OnPaint(hWnd);
  else if (false && message == WM_KEYDOWN)
    pThis->OnKeyDown(hWnd, wParam, lParam);
  else if (...)
    ...
  else
    return FALSE;
  return TRUE;
 }
};

并最终被编译器优化为:

template <class T>
class WindowMessage
{
 ...

 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  T* pThis = static_cast<T*>(this);
  if (message == WM_PAINT)
    pThis->OnPaint(hWnd);
  else
    return FALSE;
  return TRUE;
 }
};

特别地,如果WindowMessage派生类没有响应任何消息,则优化后DispatchMessage为一个空函数,如下:

template <class T>
class WindowMessage
{
 ...

 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  return FALSE;
 }
};

这就是WINX的消息分派机制为何比MFC、WTL以及其他任何界面库高效(无论是编译后的代码尺寸上,还是执行效率上)的原因。

好了,现在该是解释WINX如何做到这一点——检测派生类是否重载某个函数的时候了。我们假设,基类(名为Base)中有一个成员函数Func(假设有两个参数),现在有另一个成员函数Caller希望根据派生类是否重载Func来做事情。如下:

template <class T>
class Base
{
 RetType Func(ArgType1 arg1, ArgType2 arg2) { ... }
 void Caller() {
  if (派生类重载了Func) { ... }
  else { ... }
 }
};

一个办法是,略微修改一下基类中的Func原型,加上一个无用参数int unused:
 RetType Func(ArgType1 arg1, ArgType2 arg2, int unused = 0);
或者直接改为可变参数:
 RetType Func(ArgType1 arg1, ArgType2 arg2, ...);
当然,派生类重载Func原型还是需要按我们预期的:
 RetType Func(ArgType1 arg1, ArgType2 arg2);

如此,判断“派生类是否重载了Func”就变成了判断函数原型是否为
 RetType Func(ArgType1 arg1, ArgType2 arg2);
而这正是编译器的拿手好戏。

最后提醒一下,阅读WINX源代码时,你可以发现这个技巧有不少变种(消息分派的实现就与此有细节上的不同),但是其中的道理是完全一致的。

WINX的消息分派机制(续)

和MFC、WTL等界面库不太一样的是,WINX认为消息分派是一个可独立于窗口存在的基础服务。所以WINX中负责消息分派的不是 winx::Window<T>类,而是 winx::WindowMessage<T>类。 winx::Window<T>只是从winx::WindowMessage <T>继承。

上一篇我故意买了个关子。如果有读者在看了《WINX的消息分派机制 》一文后去亲自看winx的头文件了解实地了解一下的话,我将觉得很安慰。这一篇我们继续这个话题。

WindowMessage<T>的基本规格是这样的:

template <class T>
class WindowMessage
{
 void OnDestroy(HWND hWnd);
 void OnPaint(HWND hWnd);
 void OnKeyDown(HWND hWnd , UINT uVKChar, UINT uKeyData);
 ...

 LRESULT InternalDefault (
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam);

 BOOL DispatchMessage (
   HWND hWnd , UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult);

 LRESULT ProcessMessage(
   HWND hWnd, UINT message ,
   WPARAM wParam, LPARAM lParam);
};

WindowMessage<T>的契约,其客户必须将发送给窗口的所有消息全部转发给ProcessMessage函数进行处理。涉及的几个关键函数功能如下:

  • ProcessMessage函数先调用DispatchMessage对消息分发,如果DispatchMessage 没有处理该消息,则调用InternalDefault来处理该消息。
  • DispatchMessage函数根据消息的ID,即UINT message参数,对消息进行分派。例如WM_PAINT消息发送给OnPaint处理,WM_DESTROY发送给OnDestroy处理,WM_KEYDOWN发送给OnKeyDown处理等等。
  • InternalDefault函数则是抽象不同类型的窗口。对于普通窗口,它调用DefWindowProc;但对于MDIFrame窗口,它需要调用DefFrameProc;对于MDIChildFrame窗口,则调用DefMDIChildProc;对于对话框,则它只需要直接返回FALSE即可。
显然,这里面最为关键的是 DispatchMessage 。如果不考虑优化,它看起来并不复杂。我们这里实作一个基于虚函数(virtual)机制的版本:

class WindowMessage
{
 virtual void OnPaint(HWND hWnd) { Default(); }
 virtual void OnKeyDown(
   HWND hWnd, UINT uVKChar, UINT uKeyData) { Default(); }
 ...
 
 LRESULT Default();
 
 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  switch (message)
  {
  case WM_PAINT: OnPaint(hWnd); break;
  case WM_KEYDOWN: OnKeyDown(hWnd, wParam, lParam); break;
  ...
  default: return FALSE;
  }
  return TRUE;
 }
};
 
你一定对此不屑一顾:除了Default函数看起来有点意思(容后介绍它的实现)外,用一个超庞大switch..case来实现DispatchMessage,有"创意",但实在是有些乏味。
 
有人马上提建议说,改用模板(template)吧�D�D性能高些。于是,就有了基于template的版本:
 
template <class T>
class WindowMessage
{
 void OnPaint(HWND hWnd) { Default(); }
 void OnKeyDown(HWND hWnd, UINT uVKChar, UINT uKeyData)
  { Default(); }
 ...
 
 LRESULT Default();
 
 BOOL DispatchMessage(
   HWND hWnd, UINT message,
   WPARAM wParam , LPARAM lParam, LRESULT& lResult)
 {
  T* pThis = static_cast<T*>(this);
  switch (message)
  {
  case WM_PAINT: pThis->OnPaint(hWnd); break;
  case WM_KEYDOWN: pThis->OnKeyDown(hWnd, wParam, lParam); break;
  ...
  default: return FALSE;
  }
  return TRUE;
 }
};
 
在很多时候,模板(template)与虚拟(virtual)是相通的。这两个版本的WindowMessage并无本质的不同,事实上只是作了机械的代码变换而已。了解这个变换的等价性是很有必要的。不可否认,经此一变,性能提高了不少。前文《 ATL界面类——兼谈多态与泛型 》我们对此作了细述。
 
这个基于模板的WindowMessage类同样乏味。尽管没有了虚函数调用的开销,但这个DispatchMessage函数无疑仍然是个庞然大物,与WTL的精巧相去甚远。
 
那么,我们还有甚么办法可想吗?
 
to be continued...

星期三, 十一月 01, 2006

WINX的消息分派机制

WINX的消息分派是卓越的。我们先简单回顾一下WINX的SDI风格的Hello程序与MFC/WTL/SDK的对比(我们关注的是窗口类中的消息处理相关):

MFC和WTL有着类似MessageMap(尽管内部机制大不一样),是通过宏实现消息分派的。也许你已经习惯了响应消息时提供MessageMap,但在WINX中这不需要响应任何消息你均只需要直接覆盖消息处理函数即可。示意如下:

class MyWindow : public winx::Window<MyWindow>
{
public:
 void OnPaint(HWND hWnd) { ... }
};

你可能担忧WINX的消息分派的便利,是牺牲性能为代价的。——可是我郑重告诉你,这种担忧是多余的。随着本文对winx消息机制的一步步剖解,你将发现,事实恰恰相反,WINX在消息分派的性能上考虑甚多,其消息分派的代码的无论是编译后的执行代码尺寸,还是效率,均优于MFC、WTL。

to be continued ...

星期日, 十月 29, 2006

关于blogspot.com被封

出差中,无意中发现我的Blog(blogspot.com)不能访问了。网上搜索了一下,确认被封。暂时的解决方案如下:

你可以使用镜像网站进行访问:http://www.pkblogs.com/winxcn
感谢fireseed提供该信息:http://groups.google.com/group/winxcn/msg/4d85a8382e240db0

你也可以使用以下方法:

为了访问winxcn.blogspot.com,可通过修改hosts文件进行访问。具体方法是:编辑WINDOWS\system32\drivers\e­tc\hosts 文件,然后加入72.14.219.190 winxcn.blogspot.com,保存即可。对于其他BlogSpot的博客类似,只要将winxcn改为相应的用户名。例如: 72.14.219.190 xushiwei.blogspot.com。

星期五, 十月 20, 2006

贺:WINX发布两个月整

2006-8-20日,WINX发布了它的第一个包:winx-1.0.01
2006-10-20日,也就是今天,WINX发布刚好两个月整。

小小庆贺一下。
本Blog六天后也将迎来这一天,一并Congratulations!

星期四, 十月 19, 2006

ATL界面类——兼谈多态与泛型

由于业余时间相对比较少,并且接下来要出差(大约十天左右),看样子winxcn.com短期内不能出来。为了WINX的文档不至于遥遥无期,我决定还是现在开始在blog上连载WINX的核心文档。或许对于这些文档而言,blog不是一个很好的载体,因为blog更关注的是社会性,强调的是参与,而体系性较差。

这是开篇第一篇。你可能觉得惊奇——不是要讲WINX吗?怎么,讲ATL来着?勿须奇怪,WINX是基于ATL/WTL的,所以,在讲WINX的原理之前,先要了解ATL的基本概念,它们一定程度上是相通的。

ATL/WTL的界面开发,网上的教程还是少了点。相信大家都知道《MFC程序员的WTL开发指南》,这的确是一套难得的好教材。作为WINX的入门篇,我们这一篇是回顾《MFC程序员的WTL开发指南》第一篇:“ATL界面类”(注:为了不至于重复制造轮子,我这里不再赘述该文章已经叙述较为详细的一些技术细节)。

接触ATL/WTL久了,类似下面这样的代码你一定见得多了:

class MyClass : BaseClassT<MyClass>
{
};

把派生类MyClass,作为模版参数传递给基类BaseClassT,这种写法多多少少显得有点古怪。但这是合法的。之所以要这样,是因为我们要实现“编译期的晚绑定”(ATL界面类中把它称为“编译期间的虚函数调用机制”,这是不太准确的说法)。

基于虚函数技术的“运行期(Runtime)的晚绑定”大家都已经很熟悉了,这也就是OOP中所谓的“多态”。而基于模板技术,所依赖的并非vtable,而是类型不确定型(类型晚绑定),来达到“编译期的晚绑定”。这也就是所谓的“泛型”。

选择“多态(虚函数技术)”,还是选择“泛型(模板技术)”?

模板的好处是“零开销”——即额外的时间、空间开销均为零。而虚函数技术在时间上多了一次间接调用(由vptr找到vtable中相应的函数地址),并导致inline失效(无法展开);在空间上每个类对象多了4字节(这里假设是32bit系统)的vptr,全局多了一个vtable(大小与该类的虚函数个数有关)。

虚函数的好处是“可定义二进制规范”,从而建立语言无关的调用约定。这就是COM(组件对象模型)技术关注的内容。进一步来说,虚函数技术比较适合应用于大型程序中的模块划分,用以描述组件间调用契约——interface(接口)。

我并不是模板技术的推崇者。应当认识到,模板与虚函数技术一样,只适合有限的场合。WINX中,消息分派机制使用了模板,是因为它是最适合的人选。

作为结束,我提醒读者,理解泛型中的“编译期的晚绑定”是重要的,如果你希望深入理解WINX的话。下一节我们开始进入最关键的章节——WINX的消息机制。

星期三, 十月 18, 2006

Blogger(beta) - 无尽深处的体验...

使用blogger的最初原因是因为MSN Space太慢了,害得我不得不搬家。刚使用blogger的那会,最大的体验是“够简单”,甚至是“太简单”。有一次想要使用表格发现,blogger居然不支持,觉得很不可思议,很过分。

然而,随着使用的深入,越来越觉得blogger“很不简单”。下面让我们细数blogger的种种特色:

  1. 快。Google显然作了极大的努力来优化blogger.com的页面种种细节,以保证它足够的快。这也是我使用众多Google Service感受最深的一点

  2. 灵活的模版机制。在众多Blog站点中,blogger.com的模版是我所见最为灵活的。理论上你几乎可以随心所欲的设计你的blog外观。

  3. 万能的Rss Feed内容订阅Widgets,使你的Blog互动更出色。用它你可以实现Recent Comments,可以告诉别人你感兴趣什么(My Delicious),甚至做很多你目前还无法想象的趣事儿。

  4. Google Analytics、Google AdSense的无缝配合(我在其他如MSN Space、CSDN Blog等Blog系统上目前未找到使用Google Analytics的方式)。这其实要归功于第2.条 —— 灵活的模版机制。下面就是我的http://winxcn.blogspot.com近7天的Google Analytics的分析概要:

  5. 一个帐号可建立多个blog,亦可多人(通常是一个团队)同时维护一个blog。这看似没什么大不了,但至关重要(比支持表格或者更加友好的编辑界面重要很多)。其实这是我接触blogger后感受的第一个好处。要知道在我使用MSN Space的时代,我还专门申请了两个hotmail帐号,目的只是为了获得第二个Blog空间

关于blogger.com,我相信还有很多需要发掘的,还有更多无尽深处的体验...

谈论“PostShow网络侵害图片版权”事件

PostShow网络侵害图片版权”最近相当热闹。风言疯语之IT罗盘亦在其Blog上谈论了该事件:PostShow事件:美丽 丑恶 功利 无聊 期望。他在报道该事件的同时援引了图片,须不知自己也无形中发生了侵权行为。关于该图片的授权声明如下:

You have limited rights to personally view the images with your web browser and to use them as your personal computer wallpaper (or background image) on your own computer. These photos may not otherwise be reproduced, distributed, cropped, resized, or otherwise altered without the written permission of the photographer. No commercial use of these photos may be made in any way. All rights are reserved.

You may not use these photos on any web page, commercial or non-commercial, for profit or non-profit, without written permission from the photographer. You may however link to the photos in the manner described below. …

详细参考: http://www.airliners.net/usephotos/

类似的侵权行为完全可能会发生在我身上。远的不说,就说在引用图片、文章、源代码或其他网络资料时,我真的认真的去了解其出处及相关的版权声明了吗?我很惭愧因为我并没有。所以我认为这场闹剧很有价值,着实可以让我们反省一些自身觉得相当习惯的行为。

互联网是很特殊的媒体,你很难真的做到我们熟知版权声明方式“保留所有权利”(All Rights Reserved)。对它的滥用意味着任何人都可能触犯版权法律,意味着很多优秀的作品将无法得到最大价值的利用或最广泛的传播。现在,越来越多的人们开始意识到自己并非需要保留所有权利,相反他们更愿意选择“保留部分权利”(Some Rights Reserved),甚至“不保留权利”(No Rights Reserved)。

互联网上一种很常见(但绝不是唯一)的一种授权———创作共用授权Creative Commons),其出发点就是Some Rights Reserved,有兴趣的读者建议看一看。

星期六, 十月 14, 2006

WINX-1.1.01 Released

修订记录 1.1.01 (2006-10-14)
-----------------------------------------

*) 示范代码(tutorials)

- Hello, SmartWin! tutorials/winx/step001/hello,smartwin (a)
- XSL转换(XSLT) tutorials/winx/step018-xslt (b)
- 直方图均衡化(OpenCV样例) tutorials/opencv/step001-histgram (c)

*** 注意 ***

要编译标记了(a)的那些样例,你需要下载从sourceforge下载SmartWin++:
http://sourceforge.net/projects/smartwin/

并且让目录树看起来是这样的:

├─winx
│ └─include
├─wtl
│ └─include
└─smartwin
   ├─lib
   └─include

要编译标记了(b)的那些样例,并且你使用Visual C++ 6.0,你需要更新你的
平台SDK(Windows Platform SDK)。

你可以到http://sourceforge.net/projects/winx/下载winsdk.zip。
并且让目录树看起来是这样的:

├─winx
│ └─include
├─wtl
│ └─include
└─winsdk
   └─include

要编译标记了(c)的那些样例,你需要下载从sourceforge下载OpenCV:
http://sourceforge.net/projects/winx/
或者:
http://sourceforge.net/projects/opencv/

并且让目录树看起来是这样的:

├─winx
│ └─include
└─opencv
   ├─bin
   ├─lib
   └─include

*) WINX扩展组件

- msxml
XSL转换(XSLT)

- OpenCV
class CvWindowT, CvWindow, CvMainFrame

星期四, 十月 12, 2006

WINX中如何动态创建窗口?

现在,我们回答smithfox提的第二个问题:如何在MyCustomControl中再实现,鼠标右键处动态生成一个AnotherMyCustomControl,进行操作后消失,回来MyCustomCon­trol呢?

这个问题包含三个要点:

  • 如何响应鼠标右键点击消息?你可以响应OnRButtonDown,或者OnContextMenu。
  • 如何动态创建窗口(控件)?
  • 如何让控件的行为像菜单一样,鼠标点击到本控件之外就消失?你可以让控件捕获鼠标消息(SetCapture),从而可以获得本控件外的鼠标消息(OnLButtonDown),并在检测到鼠标消息发生在窗口外时销毁自己(DestroyWindow)。

WINX强调的是可视化,因此一直以来,均未提及动态创建窗口这个话题。要动态创建窗口,可以有三种方式:

  • 使用Windows API:CreateWindow/CreateWindowEx。其实对话框就是通过这种方式创建它的子控件的。
  • 使用窗口句柄类(winx::WindowHandle)的Create函数。
  • 使用窗口类自身的Create函数。

后两者Create函数只是对Windows API - CreateWindowEx的简单包装。这里我们给一个动态创建IE控件的例子

如何实现Custom Control?如何进行可视化界面开发?

这里,我们要回答smithfox在winx论坛上提的第一个问题: 在WINX内,怎么写一个Custom Control,也就是说自己实现Paint的Control。

其实,winx不只是可以实现自定义控件,而且允许你将这些控件直接放到对话框中,就如你放置一个Static、Button、Edit等等标准控件一样。winx的tutorials中有专门的demo告诉你如何作到这一点。

虽然目前没有专门针对WINX进行可视化界面开发的软件,但是你可以在Visual Studio中使用WINX进行半可视的开发。虽然简陋,但是在最让人烦心的控件布局的所见即所得(WYSIWYG)上,基本上还算可以。

WINX支持三类自定义控件(Custom Control):

1)完全自定义的控件(通常从winx::Window派生)。代码参考:
tutorials/winx/step004-user-ctrl/1.basic/hello.cpp

2)从现有控件派生(通常从现有控件类winx::Static、winx::Button、winx::Edit等派生),对现有控件进行细节上的行为修改。代码参考:
tutorials/winx/step004-user-ctrl/2.superclass/hello.cpp

3)通过多种现有控件组合出一个复杂的自定义控件。如何做到这一点?
其实,有一个与Delphi之Form类似的概念,就是非模态对话框(Modaless Dialog)。所以,这一类控件我们通常从winx::ModalessDialog派生。这样,你就利用Visual Studio的对话框编辑器,进行这类复杂的自定义控件的可视化开发。代码参考:
tutorials/winx/step004-user-ctrl/3.superdialog/hello.cpp

看了这几个demo,并尝试过运行这些demo的人,多多少少会有magic的感觉。让人感到疑惑的主要是,“这些自定义控件是如何被创建出来的?”

的确,在代码中,你看不到创建自定义自定义控件的代码。这就如你看不到标准控件(如Static、Button等)的创建代码一样。——在点到这一点后,灵光在你心中一闪:是的,你猜得没错,这些自定义控件和标准控件一样,是对话框加载对话框资源后自动创建的。

Visual Studio的对话框编辑器除了标准控件如Static、Button、Edit等外,还有一个特殊的控件,叫Custom Control。插入该控件,并且将Class设为要创建的控件类的类名(也就是你实现窗口类时通过WINX_CLASS指定的)。如下图(我们假设WINX_CLASS为“MyView”):

这样,对话框加载资源后,就明白需要创建一个窗口类类名为“MyView”的窗口。正因为这样,所以“MyView”这个窗口类需要在对话框生成之前注册(RegisterClass)。

——现在,你已经明白,我为什么说,WINX特别强调窗口类以及注册窗口类的概念。因为,WINX区别与传统界面库(WTL/ATL、MFC等等)的另一个显著特征是,WINX中的用户自定义控件(Custom Control)与标准控件一样,是可以直接放到对话框上创建的。这很有趣。你认为呢?

星期五, 十月 06, 2006

WINX中使用OpenCV的一个样例

上一篇我们谈到WINX与其他库共存的问题。空口无凭,我们这里就给出一个WINX中使用OpenCV的样例。

数字图像的直方图均衡化是常用的图像增强方法。这个样例本身的源代码取之:数字图像的直方图均衡化(C/C++源代码)。作者HUNNISH,是OpenCV方面的方家。

样例的源代码:
winx/tutorials/opencv/step001-histgram/hello.cpp - 使用HighGUI
winx/tutorials/opencv/step001-histgram(winx)/hello.cpp - 使用WINX

为了比较,我们提供了两个样例,其一使用OpenCV自带的HighGUI,另一个使用WINX提供的窗口。WINX提供的窗口(winx::CvWindow)是新增的一个窗口类。其代码参考:winx/OpenCV.h。这是我在WINX中新加入了一个头文件。提供这样的头文件只是为了方便,与WINX体系并无关系,多数代码也是取自HighGUI。

为了编译这两个样例,你需要下载OpenCV。你可以从http://sourceforge.net/projects/winx/下载它,并让目录结构看起来是这样的:

 - opencv
  - bin
  - lib
  - include
 - winx
   - lib
   - include

星期四, 十月 05, 2006

WINX支持DirectX,OpenCV吗?

偶尔也会听到这样的一些疑问:WINX支持DirectX,OpenCV吗?也会听到SmartWin支持OpenCV这样的说法。下面我们分析一下这个问题。

我们知道,库之间共存的障碍,主要有以下几点:

其一:编译期的符号(指类名、函数名、宏名等)冲突。主要表形在:

  • 宏名冲突(由于没有命名空间的保护)。
  • 基本类型的typedef。有不少库喜欢自己typedef一下所有的基本类型。如uint32, int32等。由于这些类型非常常见,并且typedef发生在全局命名空间,冲突的概率就很大。

其二:链接期的符号冲突。根据我的经验,这主要表现在:

  • 库之间的符号冲突,即两个库同时提供了某个函数。最为典型的是全局new/delete算符的重载C++允许重载全局new/delete算符,这真的是一场灾难。在两个库同时重载了new/delete时,就出现了符号冲突(可能也会在编译期体现,但常见的情况在链接期才表现出来)。
  • 使用了不同模式的C库。如果两个静态库(Static Library)使用了不同模式的C库,那么他们将出现大量的符号冲突。而我们知道,Visual C++提供了6种模式的C库:
      Single-Threaded
      Debug Single-Threaded
      Multithreaded
      Debug Multithreaded
      Multithreaded DLL
      Debug Multithreaded DLL
  • 不同编译器编译的静态库(Static Library)不能共存。原因主要亦在于使用了不同的C库。

其三:框架的假设。一些库是以框架方式提供的,要求用户按照其预定的方式进行调用。如果两个库均提供了框架,那么他们互不知晓的情况下,通常很难在一起工作。

其四:调用的假设。在你调用一个库的代码时,你需要模拟出它需要的环境。可分为两种情形:

  • 显式的依赖。例如,你要调用QT的函数,很多时候,你需要in/out一个QString参数。当然,既然你用了QT,生成一个QString还是很容易。但是如果某个函数要求传入QWidget*指针呢?除非你的窗体(Widget)本来就是QT实现的,不然这个QWidget*的生成还是颇费脑筋。
  • 隐式的依赖。例如,你要调用MFC的一段代码,而该代码使用了AfxGetApp或者其他。那么显然你的程序需要有AfxGetApp。你的本意可能只是需要一个MFC组件,最后你却发现,最终你不得不依赖一个MFC框架(Framework)。又如ATL/WTL的_Module,ATL/WTL本身架构精巧,但是_Module很要命。个人认为它破坏了ATL/WTL本身的纯洁。因为它其实与AfxGetApp并无二致,最终导致了强耦合的结构。

WINX如何解决这些问题?

其一:编译期的符号冲突。WINX使用namepsace尽量减少对全局命名空间的污染。对宏名亦采用类namespace的解决方案,多数WINX的宏名均以“WINX_”开头。

其二:链接期的符号冲突。和STL一样,WINX的代码尽量以纯头文件的方式提供。

其三:框架的假设。WINX不是框架,以便有更好的适应性。

其四:调用的假设。WINX的函数规格尽量减少采用WINX特有的数据结构。另外,类似MFC的AfxGetApp(),WTL的_Module的全局性数据,这在WINX中是明令禁止的(只是由于WINX使用部分WTL的代码,一些时候,_Module的使用不能避免)。

所有这一切,均是为了让WINX在最大程度上和更多的库可以协作。而我们在前面也已经提到了,WINX尽量采用了更为开放的结构。相比之下,它更懂得与其他库一起协作的道理。

星期三, 十月 04, 2006

对比WINX,WTL,MFC,SmartWin代码效率

我们以Hello, World! 程序为例,对比一下各个界面库的代码效率。对于界面程序,个人认为空间效率较之时间效率要占据主导因素,故此这里比较的是空间效率。另外,由于优化的极限是直接用Windows SDK,故此对比亦加入Windows SDK作为参考。参与此次对比的有:

  • WINX
  • WTL
  • MFC
  • SmartWin
  • Windows SDK

功能:Hello, World!

界面:模态对话框

编译器:Visual C++ 2005

源代码:

比较结果:

首先,我们对比一下静态链接多线程模式的C库——即MultiThread(MT)时的情形。MFC亦以静态链接方式链接。由于所有的代码均静态链接进去,这种方式无疑是最公平的。对比结果如下:

  • Windows SDK:48.0 K Reference:kernel32.dll, user32.dll
  • WINX:52.0 K Reference:kernel32.dll, user32.dll
  • WTL:76.0 K Reference:kernel32.dll, user32.dll, advapi32.dll, ole32.dll, oleaut32.dll
  • SmartWin:132.0 K Reference:kernel32.dll, user32.dll, comctl32.dll
  • MFC:184.0 K Reference:kernel32.dll, user32.dll, advapi32.dll, gdi32.dll, oleaut32.dll, shlwapi.dll, winspool.drv

可以看出,WINX产生的代码效率最高,并非常接近Windows SDK,而WTL则次之。SmartWin虽然以模板构建,但是比之MFC并无太大的优势。

我们再来比较一下动态链接多线程模式的C库——即MultiThread DLL(MD)时的情形。MFC采用动态链接方式。这是大型程序典型的链接方式,因此这个比较结果也颇有意义。

  • Windows SDK:6.0 K Reference:kernel32.dll, user32.dll, msvc80.dll
  • WINX:7.0 K Reference:kernel32.dll, user32.dll, msvc80.dll
  • WTL:28.5 K Reference:kernel32.dll, user32.dll, msvc80.dll, advapi32.dll, ole32.dll, oleaut32.dll
  • SmartWin:由于SmartWin编译的lib中没有MultiThread DLL(MD)模式,这里未针对其进行比较。
  • MFC:10.5 K Reference:kernel32.dll, user32.dll, msvc80.dll, mfc80.dll

尽管MFC采用动态链接mfc80.dll的方式,但是它生成的代码仍然不及WINX短小。

星期三, 九月 27, 2006

AOP, Signal/Slot, and Decoupling

解耦(Decoupling)是一个永恒的话题。本来没有打算这么早开始涉及“大型程序解耦”这一块内容,但是smithfox在winxcn论坛上提及相关的话题,所以决定还是在这里聊聊我对“解耦”的一些看法。

面向方面编程(AOP,Aspect Oriented Programming)思想的精粹,在于提倡人们尽量对功能进行切片,形成一个个独立的服务。而后,通过组合的方式,把这些服务组装成为所需要的组件。AOP的关注点在于复杂对象(或系统)的解耦(Decoupling)问题

信号-槽(Signal-Slot)机制,是希望提供一个统一的、可伸缩的方式,来规范组件之间的通讯机制。Signal-Slot的关注点在于组件间的解耦(Decoupling)问题。Delphi、C#、QT、SmartWin(Boost)均提供了Signal-Slot机制。另外,COM的ConnectPoint规范亦属于Signal-Slot范畴。

对于在SmartWin的中将消息(或称为“事件”)归类为一个个Aspect,并且采用Singal-Slot方式提供,你怎么看?WINX的消息机制为什么不采用类似SmartWin的Signal-Slot机制?这个问题可以从以下四个角度来回答。

其一:兼容。我已经说过,WINX的一个基调,是要让现有的MFC用户感到熟悉、感到Happy。所以,我不能够采取MFC用户比较陌生的Signal-Slot来进行消息处理。

其二:效率。SmartWin的消息机制无疑使得窗口对象的尺寸迅速膨胀,并且消息分派的效率大幅降低。

其三:Signal-Slot最主要的关注点是组件间的解耦(Decoupling)。一般情况下,我们主要将其用于两种对象(或多种对象)之间的消息通讯。Delphi、C#、QT在这一点上的度把握的相当好。而SmartWin将Signal-Slot机制应用于窗口自身内部的消息分派,让人有点“杀鸡用牛刀”之感。

其四:从AOP角度看。AOP的关注点是提供服务(功能切片),SmartWin只是将消息归为Aspect,并未提供服务,看起来这一个个Aspect主要是出于实现上重用的考虑,个人认为意义不大。真正AOP思想的贯彻者是ATL/WTL(当然,SmartWin既然支持了所有消息的Signal-Slot,自然也可以实现一个个的“功能切片”,尽管我还没有看到,但这可能是因为我不熟悉SmartWin的缘故)。ATL/WTL的消息分派中的MessageMap Chain机制,使得消息处理可以按功能切片进行分割,并最后可以完美的组装在一起。ATL/WTL中这样的“功能切片”太多了(有点吹牛了,其实不多:-),我们可以随意举几个例子:
  - WTL::CDialogResize (窗口布局,不只用于Dialog的Layout)
  - WTL::CDoubleBufferImpl (支持双缓冲Paint机制)
  - WTL::CThemeImpl (支持XP Theme)
  - ...

接下来我们谈谈WINX中大型程序的解耦(Decoupling)。我曾经在C++程序员的困惑一文中提到这个问题(不过这个问题不是C++程序员所特有的),并且把它作为WINX的一个要解决的目标。我在这方面做过尝试,并获得了一定的成果。但是很抱歉,它离我的期望还有一定的差距,关于这一部分的代码目前并未开放。

最后,我要附带对比一下各种的Signal-Slot实现。

Delphi、C#都是从语法角度来支持Signal-Slot机制,其性能、便利、友好程度,显然都到了最佳(Delphi为了效率,每个Signal只支持一个Slot)。QT虽然基于C++,但是其Signal-Slot机制也是从“半语法的角度”来提供(所以就有了moc预处理)。

根据我的猜想,SmartWin的作者正是觉得QT的做法不太纯洁,而试图提供一个标准C++的解决方案。但是,有两个原因让我觉得SmartWin(或者Boost)的Signal-Slot机制不好:

其一:Singal-Slot是一个通用的解耦机制。它将应用到各种层次的组件,而不会只是用于窗口消息处理。因此,Singal-Slot机制的简洁、易用是很重要的。这让我倾向于QT的Singal-Slot实现(只是相比SmartWin、Boost而言)。

其二:Signal-Slot既然关注于组件间的解耦(Decoupling),我个人倾向于它是一个二进制的规范,而不是C++ 模版定义的规范。原因很简单:我不想假设所有的组件实现者均喜欢C++。

.NET平台和Java平台最大区别在哪里?把你的焦点从C#与Java的比较上脱离开来吧。其实两者最大的区别在于,.NET平台推的是其二进制规范CLR(从COM二进制规范延伸),而Java平台推的是Java语言。微软是聪明的。呃,我把话题扯得远了。

星期一, 九月 25, 2006

新域名winxcn.com预告

http://winxcn.com

网站建设中,敬请关注。

星期日, 九月 24, 2006

WINX与ATL/WTL/MFC的关系,以及跨平台问题

说WINX基于ATL/WTL,其实不是准确的说法。实际上WINX的最核心组件(Windows、Dialog、Control)与WTL没有任何关系。只是WINX的句柄类(CWindow、Gdi句柄如CPen等)、资源类(CMenu等)是WTL的实现。

虽然说从重用角度来讲,WINX确实是:ATL -> WTL -> WINX。但对于WINX的性能是不需要担心的。WINX之所以基于WTL,完全是因为并不希望重复制造轮子。但是WINX的窗口机制是独特的,完全区别于现有各种界面库。对COM的连接点事件(ConnectPoint)更是比ATL好得多,使得C++可以如VB、C#一般方便地接收来自ActiveX控件的事件。

我在开发WINX的时候,参考了众多的界面库,如:MFC、ATL/WTL、QT、wxWidgets、SmartWin++,还有sourceforge上的win32gui、vgui等。

我所遇到的第一个问题,是兼容谁的问题。这里的兼容主要是指使用界面的兼容。这个问题不是谁说了算。看看google趋势:


可以看出,MFC、QT用户占了绝大多数。这就为WINX的开发建立了基调:尽量让MFC、QT用户感到熟悉、感到Happy。

另外,我发现一个值得注意的问题:几乎所有的库均有一个“不良倾向”,就是让自己包罗万象,给用户一个完整的解决方案 —— 网络、界面、自动化、XML、OLE等等。 特别是QT、wxWidgets,这种倾向及其明显。

为了明确我不是万能的,WINX不是万能的,在开发之初,我就给WINX一个规则:接纳现有的库,如果不能够提供得更好,就告诉(建议)用户用什么。如果某个问题有多个卓越的解决方案,那么,用户可以自由选择其中一个。

所以,在WINX中,库的关系是平行的:
 WindowSDK:Gdiplus (GDI+)、MSXML、等等
 Xerces-C
 DirectX
 ATL/WTL
 MFC
 STL
 Boost
 Loki
 ...
 WINX

而不是:
   /--- WindowSDK、Gdiplus、MSXML、DirectX
WINX ---- ATL/WTL
   \--- STL、Boost、Loki、Xerces-C

试图让自己成为用户唯一所见,这是一个危险的想法,我认为是这样。

举个例子,用户希望读取xml文件。那么用MSXML,还是用Xerces-C,还是expat?我不能确定用户想要什么。有一点可以肯定的是,WINX不会开发出另外一个XML Parser。如果WINX的代码需要一个XML Parser,我会尽量在一个比较抽象的层次,为以上三者提供一个共同的界面(当然,不是抽象所有的功能,只是WINX所需要的),就如我们抽象WINX_ASSERT一样。

我们回头看WINX_ASSERT这个例子。WINX中是这么实现的:

#if defined(ASSERT)
#define WINX_ASSERT(e) ASSERT(e)
#elif defined(_ASSERTE)
#define WINX_ASSERT(e) _ASSERTE(e)
#else
#ifdef _DEBUG
#define WINX_ASSERT(e) assert(e)
#else
#define WINX_ASSERT(e) 0
#endif
#endif

这意味着什么?ASSERT来自MFC,_ASSERTE来自MSCRT(参见crtdbg.h)、assert来自C标准库。这里没有检测ATLASSERT,是因为ATLASSERT就是_ASSERTE。

尽管实现ASSERT功能并不复杂(肯定远远简单于实现XML Parser),但是我决定还是不自己去做。原因很简单:我没有打算对它作出改进。

最后我们谈谈跨平台。其实我一直在尝试找到一个方案,可以把平台的差异屏蔽。然而,有一点让我感到不安,因为没有谁可以真的做到平台无关,无论我作出多少努力。我要在Linux平台(甚至更多的平台)提供提供Gdiplus(Mono在做这件事)?DirectX?COM?OLE?还是我去告诉用户,不要用Gdiplus,不要用OLE,不要用COM,喏,这里有一个跨平台的方案,你照着办吧。

我会尝试让WINX跨平台。当然我相信只是最核心部分值得这样做。问题的重心仍然在于,你无权阻止用户喜欢用Gdiplus来开发。所以,跨平台的方案,永远有很多种。

星期一, 九月 18, 2006

winx-1.1(stable version)发布

第一个winx-stable版本发布。

接下来一段时间内不会发布新的版本,主要以修改一些反馈的bug为主。winx发布虽然只有二十几天,但是在内部使用已经有较长时间,应该说功能、接口均相对比较稳定。事实上有更多功能是暂时屏蔽的。本着审慎发布一项功能的原则,那些接口需要进一步商榷,或者属于比较外围的功能,均暂时不对外发布。

下一阶段重点会补充一下文档。

星期四, 九月 14, 2006

感谢“无名”

WINX发布不到一个月,已经有一些热心的朋友在试用,并发现了一个重要bug。为了纪念,为此专门发布一个版本:

http://sourceforge.net/project/shownotes.php?group_id=174954&release_id=447445

感谢不相识的朋友。

星期三, 九月 13, 2006

005 - 窗体属性(Window Property)

摆脱丑陋的MessageMap,你不再需要进行消息映射。这是WINX带给你的第一份惊喜。

你的第二份惊喜,是一种全新(至少对MFC、WTL程序员如此)的编程体验:类Delphi的窗体属性编程。

先看几个例子:

// ---------------------------------------
// 设置对话框背景为灰色

class CHelloDlg : public winx::ModalDialog<CHelloDlg, IDD_HELLO>
{
WINX_BKGND_BRUSH(GRAY_BRUSH);
};

// ---------------------------------------
// 设置对话框背景为一幅位图

class CHelloDlg : public winx::ModalDialog<CHelloDlg, IDD_HELLO>
{
WINX_BKGND_PATTERN(IDB_BKGND);
// 这里IDB_BKGND是位图的资源ID
};

// ---------------------------------------
// 设置对话框快捷键

class CHelloDlg : public winx::ModalDialog<CHelloDlg, IDD_HELLO>
{
WINX_DLG_ACCEL();
WINX_ACCEL(IDR_ACCEL);

WINX_CMDS_BEGIN();
WINX_CMD(ID_HELP_ABOUT, OnCmdAbout);
WINX_CMDS_END();

public:
VOID OnCmdAbout(HWND hWnd)
{
winx::SimpleDialog dlg;
dlg.DoModal(hWnd, IDD_ABOUT);
}
};

我们看到,这种代码风格与MFC、WTL是十分不同的。我称之为基于窗体属性的编程风格。

首先需要指出的是,尽管这里以对话框作为例子,但是这些窗体属性是普适的,可以用于任何窗体。

只要一句WINX_ACCEL(IDR_ACCEL),搞定快捷键(WINX_DLG_ACCEL是打开快捷键功能。只需要顶层窗口调用,不是每个控件都需要。WINX_CMDXXX属于命令分派,与快捷键无关),这在WTL中非常难以办到。我们知道,WTL的快捷键机制是基于PreTranslateMessage的,而在WTL的模态对话框中根本没有PreTranslateMessage消息。所以,为了支持快捷键,你不得不改用非模态对话框。

关于窗体属性的概览性描述,请参考:

http://winxcn.blogspot.com/2006/09/002-winxproperty.html

星期一, 九月 11, 2006

004 - Hello, WINX! - 续

我们再来看看用MFC、WTL、WINX来实现的SDI窗口风格的最简单的Hello程序。

// -----------------------------------------
// MFC的Hello程序

class CMainFrame : public CFrameWnd
{
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;

cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(
CS_HREDRAWCS_VREDRAWCS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW),
HBRUSH(COLOR_WINDOW+1),
NULL);

return TRUE;
}

protected:
afx_msg void OnPaint()
{
CPaintDC dc(this);
dc.TextOut(1, 1, _T("Hello, MFC!"));
}
DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()

class CHelloMfc2App : public CWinApp
{
public:
virtual BOOL InitInstance()
{
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;

pFrame->Create(NULL, _T("Hello"));
pFrame->ShowWindow(SW_SHOW);

return TRUE;
}
};

// -----------------------------------------
// WTL的Hello程序

class CHelloMainFrame : public CFrameWindowImpl<CHelloMainFrame>
{
public:
BEGIN_MSG_MAP(CHelloMainFrame)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
CHAIN_MSG_MAP(CFrameWindowImpl<CHelloMainFrame>)
END_MSG_MAP()

LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
CPaintDC dc(m_hWnd);
dc.TextOut(1, 1, _T("Hello, WTL!"));
return 0;
}
};

WTL::CAppModule _Module;

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
_Module.Init(NULL, hInstance);
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);

CHelloMainFrame wndMain;
wndMain.Create(NULL, NULL, _T("Hello"));
wndMain.ShowWindow(nCmdShow);

theLoop.Run();
}
_Module.Term();
return 0;
}

// -----------------------------------------
// WINX的Hello程序

class CHelloMainFrame : public winx::MainFrame<CHelloMainFrame>
{
WINX_CLASS("CHelloMainFrame");
public:
void OnPaint(HWND hWnd)
{
winx::PaintDC dc(hWnd);
dc.TextOut(1, 1, _T("Hello, WINX!"));
}
};

winx::CAppModule _Module;

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CAppModuleInit module;

CHelloMainFrame::RegisterClass();
CHelloMainFrame wndMain;
wndMain.Create(NULL, _T("Hello"));

return module.Run();
}
// -----------------------------------------

如果你了解Windows SDK编程,你肯定知道一个典型的Windows窗口程序(SDI),通常包含以下三个步骤:

1)注册窗口类。
2)创建窗口。
3)消息循环:分派并在窗口过程处理相应的消息。

我们看到,MFC把注册窗口类的过程隐含在PreCreateWindow,并尽量弱化窗口类概念。这表现在MFC的AfxRegisterWndClass甚至不允许用户主动指定窗口类的名字。WTL进一步弱化了窗口类概念,用户甚至根本就不需要知道RegisterClass(注册窗口类)这回事。

WINX采取了相反的策略,强调窗口类的概念,并认为用户理解窗口类是重要的。具体表现在:

1)用户需要用WINX_CLASS指定窗口类的名字。
2)用户需要在创建窗口类的第一份实例前,主动RegisterClass

这看似麻烦,但其实它是WINX强调可视化编程的关键,这一点我们在后续文章中将详细解释。

最让你惊异的,也许是WINX没有MessageMap。是的,WINX不需要你指定如何分派消息。并且,你将发现,几乎所有的MFC中的消息,WINX中均有对应,并且两者的消息处理函数原型极其相似,基本都是多了一个HWND hWnd参数。例如MFC的void OnPaint(),到WINX中为void OnPaint(HWND hWnd); MFC中的void OnLButtonDown(UINT uFlags, CPoint pt),到WINX为void OnLButtonDown(HWND hWnd, UINT uFlags, winx::CPoint pt); 等等。关于WINX中的消息分派机制,我们后续将进一步介绍。

最后需要提醒的是,WINX目前没有提供自己的消息循环,直接取自WTL。这个例子你看到WINX的WinMain代码比WTL简洁,但这只是假象。WINX只是对WTL的WinMain进行了一定的包装,没有实质性改进。WTL完全可以提供同样简洁的代码。

004 - Hello, WINX!

提到WINX,总是感觉有太多的背景需要交代,以致于到现在才迎来我们经典的第一课——Hello程序。

一个我们已经提到过的事实是:WTL的很多关键特性在模态对话框中不可用。例如PreTranslateMessage (包括快捷键的支持)、UpdateUI等等。而与WTL对模态对话框的“轻视”相反,WINX极大化的强化模态对话框的能力。为了突出这一点,我们的Hello程序首先从模态对话框开始:

// -----------------------------------------
// MFC的Hello程序

class CHelloMfcDlg : public CDialog
{
public:
enum { IDD = IDD_HELLOMFC_DIALOG };

CHelloMfcDlg(CWnd* pParent = NULL)
: CDialog(CHelloMfcDlg::IDD, pParent) {}
};

class CHelloMfcApp : public CWinApp
{
public:
BOOL InitInstance()
{
CHelloMfcDlg dlg;
dlg.DoModal();
return FALSE;
}
};

CHelloMfcApp theApp;

// -----------------------------------------
// WTL的Hello程序

class CHelloDlg : public ATL::CDialogImpl<CHelloDlg>
{
public:
enum { IDD = IDD_HELLO };

public:
BEGIN_MSG_MAP(CHelloDlg)
COMMAND_RANGE_HANDLER(IDOK, IDNO, OnCloseCmd)
END_MSG_MAP()

LRESULT OnCloseCmd(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
::EndDialog(m_hWnd, wID);
return 0;
}
};

WTL::CAppModule _Module;

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
_Module.Init(NULL, hInstance);
{
CHelloDlg dlg;
dlg.DoModal();
}
_Module.Term();
return 0;
}

// -----------------------------------------
// WINX的Hello程序

class CHelloDlg : public winx::ModalDialog<CHelloDlg, IDD_HELLO>
{
};

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CHelloDlg dlg;
dlg.DoModal();
return 0;
}
// -----------------------------------------

是的,对比三者模态对话框的样例,你至少可以看出两点:

1)MFC框架带给MFC模态对话框的,是累赘。WTL、WINX均不提供框架,你可以按自己的意愿写WinMain中的代码。

2)WTL的代码总是看起来显得笨拙(尽管高效)。而单看对话框代码,MFC、WINX看起来比较简洁,因为他们隐含已经处理了IDCANCEL、IDOK代码。

不过,由于只有WTL写了消息处理代码,其余两者均未处理消息,这个样例对WTL而言并不公平。让我们再来看看三者提供的最原始的SDI窗体。

关于这里的各个例子,你可以在WINX提供的tutorials中找到。它们分别是:

tutorials/winx/step001/hello,mfc
tutorials/winx/step001/hello,wtl
tutorials/winx/step001/hello,winx

003 - 对比WINX与MFC、WTL

尽管对MFC有这样那样的批评(这其中也包括我本人),但是MFC无疑是C++ GUI领域最成功的。WTL源于ATL,由于它的灵活、高效,而赢得了不少人的赞赏。然而要说WTL获得了成功,个人认为言之过早。看起来WTL似乎是“叫好不叫座”,并没有多少成功的产品是真正基于WTL进行GUI开发。

仔细分析一下,这样的结果并不奇怪。虽然MFC产生的代码笨拙,但是MFC的成功除了Microsoft大力推动之外,在于它生逢其时。它简陋的可视化开发界面,和自动代码生成,在当时基本上都是全手工界面开发的情况下,立即获得了青睐。而WTL恰恰是“生不逢时”,它出现于MFC大行其道之时,并且它的关注点在于“灵活,高效”,没有向导支持,所有代码手工完成,这无疑是一种倒退(MS可以强行开发基于WTL的可视化环境,但是WTL的特性并不太适合这样做,因为它“太灵活”了,这是它的优点,也是它的致命伤)。所以WTL仅仅获得少数有特殊需求的,对生成的代码尺寸非常注重的开发人员的推崇,但是Microsoft官方却弃之不顾(试为MS设想WTL可能的推广策略),其中道理不难理解。

WINX关注一个目标:简单而高效(MOST SIMPLE BUT EFFECTIVE)。WINX肯定了WTL技术上的先进性。故此WINX基于WTL,大量重用了WTL的代码。但是WINX除了高效外,更关注的是简单(SIMPLE)。

为了使得界面开发对程序员而言更为简单舒畅,WINX关注点不是在技术上,而是在对开发手段的革新上。WINX重用WTL只是一种手段。WINX重用的是它的实现,不是它的使用界面。

针对MFC的“长处”——半可视化开发,WINX引入了类似Delphi中的属性编程,使得多数常见的效果可以以窗体属性的方式进行表示。尽管目前尚且没没有专门针对WINX进行可视化界面开发的工具,但是它是WINX的发展方向。而且即使是在目前的简陋条件下,你仍然可以象MFC程序员一样,进行半可视的界面开发。

WTL脱离MFC程序员的习惯有点远了。这也是它让初学者畏惧的一个原因。WINX其实是更接近MFC的WTL:它提供了更接近于MFC的使用界面,并努力使得MFC代码可以更加容易地移植到WINX中。关于MFC代码移植,我们已经在WINX中提供了几个样例。有一点你需要理解的是,WINX并不十分关注将一个MFC应用程序移植到WINX下,因为那只有学术价值,但并不具备商业价值,我个人并不推荐你这样做。关于MFC移植,我的侧重点始终在于移植一些具备可重用性较高的MFC代码,例如MFC编写的控件等。

WINX可以取代MFC吗?对于这个问题我只能笑笑。我没有这个奢望。对于我而言,只是试图把我对C++程序员的几大困惑(内存管理、界面编程、自动化——Automation)的解决方案公诸于世,让更多人得益于它而已。从这个意义上来说,WINX只是侧重点在于提供界面开发的解决方案,但它不只是局限于界面库的范畴。

星期六, 九月 02, 2006

002 - WINX概览:高级特性

*) 进程间通讯(IPC)
 –拖放/复制粘贴
 –IDataObject
 –暂时屏蔽

*) 自动完成(AutoComplete)
 –Edit/ComboBox控件的自动完成
 –可保存历史到Windows注册表或者文件。

*) XP风格化
• Common Control
 –用InitCommControls()和manifest文件搞定。
• User Custom Control
 –#include "winx/Theme.h"
 –需要引入theme,自己绘制出XP风格的界面。
• Menu
 –用WINX_APP_MENU指定XP风格(建议选自动),或者用WINX_APP_LOOKNFEEL。
 –暂时屏蔽
• WebBrowser
 –ActiveX控件中,IE控件的XP风格化需要调用IE控件相关接口支持。

*) 让Win2000支持XP Style?
• 这个问题我这样看:既然用户选择了Windows 2000,说明他喜欢2000的精简风格,那么你又何必自作多情非要把你自己喜爱的XP风格强加给他?
• 风格一致性。

*) 自动化支持(Automation)
• #import
 –编译器的支持
• DispObject
 –Invoke 的包装
 –暂时屏蔽
• 接收连接点事件
 –DispEventBaseImpl
 –DispEventSimpleImpl
 –WINX_SINK_BEGIN
 –WINX_SINK_DEFI
 –WINX_SINK_END

002 - WINX概览:窗口行为(WindowBehavior)

*) 窗口行为
• 可以施加于任何窗口上的行为特征。
• WINX_BEHAVIOR
• 目前支持的行为列表
 –DefaultBehavior(无行为)
 –Moveless(不可移动)
 –WithinScreen(不可出屏幕)
 –LimitScreenTop/Left/Right
 –AutoHiddenTop/Left/Right(在上/左/右方自动隐藏)
 –AutoHiddenAnySide(自动隐藏)
 –FullScreenDockable(全屏停靠在窗口的左/右方)

*) 窗口行为的切换(Switch)
• BehaviorPermit
 –是否允许一个行为
 –即:在某种行为与DefaultBehavior之间切换
• BehaviorSwitch
 –在多种行为之间切换

002 - WINX概览:属性(Property)

*) 属性
• 基于属性的编程,容易向可视化发展。
• Delphi中优良的用户体验
• 区别于Delphi:如果某个特性没有使用,不会带来额外的代价。

*) 属性的命名规则
• 规则
 –WINX[_适用范围]_属性[_属性子类]
  • WINX_BKGND_PATTERN
   –其中,BKGND为属性,PATTERN为属性子类。
  • WINX_APP_ICON
   –其中,APP为适用范围,ICON为属性。
  • WINX_SYSICON
   –有时,为了减少宏名长度,在属性子类可以表意的情况下,可以省略属性。如:WINX_SYSICON,完整名应该称为WINX_ICON_SYSICON。
• 适用范围
 –WINX_XXX
  • 该属性所有窗口均适用
 –WINX_DLG_XXX
  • 该属性只在对话框类中使用。
 –WINX_APP_XXX
  • 该属性属于Application属性,应该在WinMain函数中使用。这类属性比较特殊,属于全局设置,作用于所有窗口。
 –......

*) WINX支持的属性列表
• 快捷键(Accel)
• 窗口图标(Icon)
• 窗口背景(Bkgnd)
• 窗口子控件统一背景(CtlBkgnd)
• 窗口Resize最小大小(MinInfo)
• 窗口布局(Layout)
• 窗口锚点(Anchor - todo)
• 应用程序图标(AppIcon)

*) WINX_ACCEL
• WINX_ACCEL
 –指定一个窗口的快捷键表
• WINX_DLG_ACCEL
 – 对话框及其子控件支持快捷键,默认不支持。
• TestTranslateAccel
 – 快捷键派发机制的修正。因为父窗口可能截获子窗口的快捷键。故此提供一个修正机会。

*) WINX_ICON
• 设置窗口的图标
 –WINX_ICON
 –WINX_ICONSM
 –WINX_SYSICON
 –WINX_SYSICONSM

*) WINX_BKGND
• 设置窗口背景
 –WINX_BKGND_COLOR
  例:WINX_BKGND_COLOR(COLOR_WINDOW);
 –WINX_BKGND_BRUSH
  例: WINX_BKGND_BRUSH(GRAY_BRUSH);
 –WINX_BKGND_PATTERN
  例:WINX_BKGND_PATTERN(IDB_BKGND);
  功能:以资源文件中的一幅位图作为窗口背景
 –WINX_BKGND_NULL
  功能:透明背景。

*) WINX_CTLBKGND
• 类似于WINX_BKGND,但是是统一设置某类型的子控件背景。
 –WINX_CTLBKGND_COLOR
 –WINX_CTLBKGND_BRUSH
 –WINX_CTLBKGND_PATTERN
 –WINX_CTLBKGND_NULL
 –WINX_CTLBKGND_NULL_ALL

*) WINX_MININFO
• 设置窗口的最小SIZE
 –WINX_MININFO
 –WINX_MININFO_PT
 –WINX_MININFO_DEFAULT

*) 设置窗口布局
• 提供以下方案:
 –WINX_DLGRESIZE_BEGIN
 –WINX_DLGRESIZE
 –WINX_DLGRESIZE_END
• 这是利用WTL 提供的DLGRESIZE_MAP 实现,使用上略有简化。

*) WINX_APP_ICON
• 设置应用程序图标。
• 这将使得所有有标题的弹出窗口自动设置了该图标(除非该窗口使用WINX_ICON自己设置了图标)。
• 包括MessageBox。

002 - WINX概览:消息(Message)

*) 消息分派(DispatchMessage)
• 消息分派机制是WINX的特色之一。
• 区别于WTL或者MFC,你不需要主动写一个消息分派的映射表。
• WINX易用性的第一体现。
• 高性能
 –WINX 为了实现消息分派使用了一个庞大的虚表?NO !
 –对比产生的汇编代码表明,WINX 的消息分派产生的代码比WTL 和MFC 更为短小精悍。

*) WINX和WTL消息分派的比较
• 各有特色
 –WTL:强灵活性,容易进行第三方的功能扩展。缺点是看起来比较丑陋。
 –WINX:强调易用性,并且实现了用户不需要某个特性时无额外开销。故此,系统可以不断的进行新特性的添加(但是缺点是不能以第三方的方式添加—— 注:当然你可以按WTL 的方式进行功能扩展,在WINX 中这是允许的,但与WINX 的设计理念有背)。

*) 消息分类
• 位置事件(MouseEvent)
• 焦点事件(Keyboard、Accel、Command)
 –目标的不确定性,是焦点事件(Focus Message)最显著特征。
• 绘制事件(Paint/NcPaint/EraseBkgnd等)
• 定时事件(Timer)
• 通知事件
 –子通知父
 –系统通知
 –广播

*) 命令分派(Command)
• 命令事件
 –WINX_CMDS_BEGIN[_EX]
 –WINX_CMD
 –WINX_CMD_EX
 –WINX_CMDS_END[_EX]
• ForwardCommand
 –WINX_CMDS_BEGIN_EX/END_EX 配对,区别与WINX_CMDS_BEGIN/END 的是,如果消息自身没有处理,会发给活动的子视图处理。

*) 命令状态(UpdateUI)
• WTL 的命令状态维护机制不错,但是ModalDialog 不完全支持该机制。主要的问题在于没有OnIdle 消息。
• WINX的UpdateUI基于WTL的命令状态维护机制实现,并作出改进。

*) 键盘与快捷键(Keyboard&Accel)
• 和命令一样,属于焦点事件,有事件的目标窗口不确定性。
 –考虑用况:整个应用程序支持Undo/Redo,但是程序某个时刻处于一个Edit控件中,此时希望Undo/Redo是Edit控件的文本,而不是应用程序一级的Undo/Redo。
• 解决方案:
 – 使用ForwardCommand
 – 使用TestTranslateAccel (见后文)

*) 通知消息分派(Notify)
• WINX_NOTIFY_BEGIN
• WINX_NOTIFY
• WINX_NOTIFY_END

*) 反射(Reflect)
• WINX_REFLECT
 –反射,即将子窗口发给父窗口的消息返回给子窗口自己处理。反射机制通常用于解耦。
• WINX_REFLECT_CMD
• WINX_REFLECT_CMDS_BEGIN
• WINX_REFLECT_CMD
• WINX_REFLECT_CMDS_END
• WINX_REFLECT_NOTIFY
• WINX_REFLECT_NOTIFY_BEGIN
• WINX_REFLECT_NOTIFY
• WINX_REFLECT_NOTIFY_END

*) 鼠标滚轮消息
• WM_MOUSEWHEEL
 –Note: SetFocus();

002 - WINX概览:窗口类(WindowClass)

*) 窗口类(WindowClass)的注册
• MFC、WTL弱化了窗口类的注册,与此相反,WINX强调窗口类概念,并要求用户主动注册窗口类。
 –表面上看,WINX的用法比MFC、WTL繁琐了(多了主动RegisterClass过程),但是深究下去,你将发现这恰恰是WINX推崇可视化界面开发的关键点。
• 窗口类注册相关
 –WINX_CLASS
 –WindowClass::RegisterClass
• 只有普通窗口、窗口超类(Superclass)需要注册。子类化窗口、普通对话框不需要注册。
 –RegisterClass 相关的例子:
  • 普通窗口
  • 超类(Superclass )
  • 对话框超类

*) 控件列表
• Static/Button/Edit/ComboBox
• ScrollBar/FlatScrollBar
• ListBox/DragListBox
• ListCtrl/HeaderCtrl/TreeCtrl
• ToolBarCtrl/ReBarCtrl
• SliderCtrl(即:TrackBarCtrl)
• SpinButtonCtrl(即:UpDownCtrl)
• TabCtrl/ToolTipCtrl/StatusBarCtrl/ProgressCtrl
• HotKeyCtrl/AnimateCtrl/IPAddressCtrl
• DateTimeCtrl/MonthCalCtrl
• ComboBoxEx/RichEdit
• LinkCtrl

*) 使用高版本特有控件
• SafeCtrl
 –这是一个特殊的窗口类。在以下用况使用:
  • 高版本Windows提供了一个控件(例如LinkCtrl)。我们希望在该版本的Windows版本使用它,而在低版本的Windows下,我们提供一个替换控件(AltCtrl)。
  • 我们使用了一个第三方控件。我们希望在用户安装了该控件时使用它,否则使用替换控件(AltCtrl)。
• 概念
 –OrgCtrl/OrgClassName
  • 高版本Windows提供(或第三方提供)的控件
 –AltCtrl/AltClassName
  • 替换控件。你需要实现的。
 –SafeCtrl/SafeClassName
  • 安全控件。WINX提供的。例如SafeLinkCtrl。

002 - WINX概览:句柄类(HandleClass)

*) 窗口句柄类
• WindowHandle - ATL::CWindow
• AxCtrlHandle - ATL::CAxWindow
 –ActiveX控件句柄类
• StaticHandle - WTL::CStatic
• ButtonHandle - WTL::CButton
• EditHandle - WTL::CEdit
• ComboBoxHandle - WTL::CComboBox
• ScrollBarHandle - WTL::CScrollBar
• FlatScrollBarHandle - WTL::CFlatScrollBar
• ListBoxHandle - WTL::CListBox
• DragListBoxHandle - WTL::CDragListBox
• ListCtrlHandle - WTL::CListViewCtrl
• HeaderCtrlHandle - WTL::CHeaderCtrl
• TreeCtrlHandle - WTL::CTreeViewCtrl
• ToolBarCtrlHandle - WTL::CToolBarCtrl
• TabCtrlHandle - WTL::CTabCtrl
• ToolTipCtrlHandle - WTL::CToolTipCtrl
• StatusBarCtrlHandle - WTL::CStatusBarCtrl
• SliderCtrlHandle/TrackBarCtrlHandle - WTL::CTrackBarCtrl
• SpinButtonCtrlHandle/UpDownCtrlHandle - WTL::CUpDownCtrl
• ProgressCtrlHandle - WTL::CProgressBarCtrl
• HotKeyCtrlHandle - WTL::CHotKeyCtrl
• AnimateCtrlHandle - WTL::CAnimateCtrl
• ReBarCtrlHandle - WTL::CReBarCtrl
• ComboBoxExHandle - WTL::CComboBoxEx
• DateTimeCtrlHandle - WTL::CDateTimePickerCtrl
• MonthCalCtrlHandle - WTL::CMonthCalendarCtrl
• IPAddressCtrlHandle - WTL::CIPAddressCtrl
• PagerCtrlHandle - WTL::CPagerCtrl
• RichEditHandle - WTL::CRichEditCtrl

*) 资源句柄类
• BitmapHandle - WTL::CBitmapHandle
• Bitmap - WTL::CBitmap
• IconHandle - WTL::CIconHandle
• Icon - WTL::CIcon
• CursorHandle - WTL::CCursorHandle
• Cursor - WTL::CCursor
• MenuHandle - WTL::CMenuHandle
• Menu - WTL::CMenu
• AcceleratorHandle - WTL::CAcceleratorHandle
• Accelerator - WTL::CAccelerator

*) GDI句柄类
• RgnHandle - WTL::CRgnHandle
• Rgn - WTL::CRgn
• PenHandle - WTL::CPenHandle
• Pen - WTL::CPen
• BrushHandle - WTL::CBrushHandle
• Brush - WTL::CBrush
• FontHandle - WTL::CFontHandle
• Font - WTL::CFont
• PaletteHandle - WTL::CPaletteHandle
• Palette - WTL::CPalette
• EnhMetaFileHandle - WTL::CEnhMetaFileHandle
• EnhMetaFile - WTL::CEnhMetaFile

*) GDI句柄类 – DC
• DCHandle - WTL::CDCHandle
• ClientDC - WTL::CClientDC
• WindowDC - WTL::CWindowDC
• PaintDC - WTL::CPaintDC
• MemoryDC - WTL::CMemoryDC
• EnhMetaFileDC - WTL::CEnhMetaFileDC

*) 其他句柄类
• ImageListHandle - WTL::CImageList

002 - WINX概览:基础支撑

*) 基础支撑的实现原则
• 不假设自己是用户独立使用的库。
• 所以尽量检测用户是否使用了某种流行的库,选择该库已有的。
• 举例:WINX_ASSERT
  #if defined(ASSERT)
  #define WINX_ASSERT(e)ASSERT(e)
  #elif defined(_ASSERTE)
  #define WINX_ASSERT(e)_ASSERTE(e)
  #else
  #ifdef _DEBUG
  #define WINX_ASSERT(e)assert(e)
  #else
  #define WINX_ASSERT(e)0
  #endif
  #endif

*) 不完全列表
• 基础类,多数来自WTL
 –CPoint
 –CRect
 –CSize
 –CString
 –CFindFile
 –CRecentDocumentList
 –WindowRect
 –ClientRect
• 通用
 –_offsetof、parent_class_ptr、countof
 –MsgBox
 –WINX_DEFINE_IID、WINX_UUID
• 调试/诊断
 –WINX_ASSERT
 –WINX_ASSERT_OK
 –WINX_ASSERT_ONCE
 –WINX_ASSERT_DERIVE
 –WINX_REPORT
 –WINX_VERIFY
 –WINX_VERIFY_OK
 –WINX_TRACE

002 - WINX概览:WINX中的基本概念

• 基础支撑
 –基础类(BasicTypes),多数来自WTL
 –辅助:调试诊断等
• 句柄类(HandleClass)
 –Win32 SDK中的句柄的简单包装
 –WINX的所有句柄类直接取自ATL/WTL
• 窗口类(WindowClass)
 –消息(Message)的接收者
 –窗口类不一定要保存有窗口句柄(!!!)
• 消息(WindowMessage)
 –消息分派(DispatchMessage)
• 属性(Property)
 –属性编程(类Delphi),易往可视化发展

002 - WINX概览:WINX的目标

• 轻巧、易用、“傻瓜式”
• 属性编程(类Delphi)
• 可视化
 – 事实:WTL 的很多关键特性在模态对话框中不可用。例如PreTranslateMessage (包括快捷键的支持)、UpdateUI 等等。
 – 与WTL 对模态对话框的“ 轻视” 相反,WINX 极大化的强化模态对话框的能力。从而使得多数的界面设计可以以可视化方式完成。

星期三, 八月 30, 2006

002 - WINX概览:C++程序员的困惑

• 内存管理
 –垃圾回收器?
 –解决方案: WINX!( AutoFreeAlloc等)
• 界面编程
 –可视化?
 –属性编程?事件?
 –解决方案:WINX!
• 自动化支持(Automation)
 –Dispatch调用?连接点?
 –解决方案:#import + WINX!
• 进程间通讯(IPC)
 –解决方案:WINX!
• 大型程序支持
 –如何解耦?Document/View?
 –WINX的界面设计理念不是MFC简单的Document/View模型。

001 - 认识WINX

WINX是卓越的,你需要了解以下内容:

*) 卓越的消息分派机制。正是因为有这个核心支撑,使得WINX区别于传统的界面库(如MFC、WTL)。

*) 简单易用(SIMPLE)是第一目标,尽量使可视化(WYSIWYG)界面开发成为可能。

*) WINX是一个界面库,不是开发框架(WINX is a library, not a framework)。WINX代码是可以和WTL、MFC等界面库的代码共存的。

*) 兼容。尽管有更简洁的方法,但WINX还是提供了MFC程序员熟悉的调用界面,并尽量使得MFC代码可以轻松移植到WINX下。

*) 不重复制造轮子。在没有一个卓越的解决方案以区别于现有系统之前,先沿用现有的。事实上,WINX建立于WTL之上,重用了多数的WTL组件。

星期六, 八月 26, 2006

欢迎,这里是winx official blog!

WINX关注一个目标:简单而高效。它是一个C++界面开发库,容易使用,兼容WTL。WTL是高效的,然而,它并不容易上手。

您可以在sourceforge获得WINX的最新版本:http://sourceforge.net/projects/winx/