【杂谈】不同客户端框架的优缺点

见解1

我试着谈下UI框架的发展路径,不见得是各个UI库在编程上的特点,就作抛砖引玉之言。

故事的开始源于上世纪70年代初施乐Palo Alto实验室对图形系统的早期研究。尽管由于种种原因施乐在图形系统领域并没有取得商业上的成功,但Palo Alto的成果为微软、苹果这样的操作系统厂商提供了最初的人才与经验储备。也就是这时,出现UI设计的3大原则:

  • 面向对象;
  • MVC;
  • 消息队列驱动;

直到现在各个UI系统,包括题主所提到的MFC、WPF、Qt,也包括其它,诸如Android SDK、Cocoa的构建仍旧建立在这3大原则的基础上。

要提到MFC,就不得不先提到Windows SDK,后者是随Windows 1.0所提供的操作系统API。Windows 1.0在1985年发售,尽管在此之前已有施乐的Star、苹果的Lisa和Mac OS这样的图形界面操作系统,但Windows 1.0毕竟是第一个大规模发行的图形操作系统,需要直面各样的开发者与普通用户的问题,也算是“第一个吃螃蟹的”。既然是第一个,当然是不成熟的。这种不成熟即有当时技术条件的限制,也有经验上的匮乏,我用一个例子简要说明一下。例如,我需要显示一个窗口,Windows SDK看上去是这样的:

int WinMain() {
    //1 register a window class
    WNDCLASS wndclass;

    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = NULL;
    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = "MainWndClass";

    RegisterClass(&wndclass);

    //2 create a winow
    HWND hwnd = CreateWindow(
        "MainWndClass",
        "Window Title",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);

    //3 message loop
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

对,你没有看错,那个叫CreateWindow的函数的有11个参数!按照面向对象的观念与简洁性的做法,它不应该是这样的吗(一个Gtk的例子):

GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

gtk_window_set_title(window, "window title");
gtk_window_set_position(window, GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_default_size(window, 400, 300);
gtk_window_set_resizable(window, TRUE);

原因是当时的机器内存太小,一个函数的链接符号要占有一个字长的地址空间,所以能省就省。这也是当时诸多系统API设计的惯例,如之后苹果的Carbon库,参数多到惨不忍睹的函数也是比比皆是。这就是所谓的“技术限制”的一例。

此外,上例中第一步“注册窗口类”和第三步“消息循环”有必要强制要求用户来指明各个参数项吗。从后来的实践看,答案是没有必要。但作为“第一个吃螃蟹的”,怎么会知道。这就是所谓的“经验上的匮乏”一例。

作为后起之秀的Qt,同样是显示一个窗口,用户代码就简洁得多:

int main(int argc, char *argv[]){
    
    //1 create application instance
    QApplication app(argc, argv);

    //2 create a window
    QWidget *window = new QWidget;
    window->setWindowTitle("title");
    window->show();

    //3 message loop
    return app.exec();
}

正由于Windows SDK的不如意,于是就出现的MFC。

这里我想插一个题外话:为什么用户终端系统(包括Windows、mac OS这样的PC端,Android、iOS这样的移动平台,也包括Play Station、xbox这样的家用主机),所提供的的API和程序库接口,编程语言都是类C(C、C++、ObjC、Java等)的。这主要是技术积累的缘故。早期探索用户终端系统的开拓者,使用的C语言,先驱们在这一语言基础上构建起各种基础设施,包括教育环境与人才储备,项目管理的流程与经验,开发、分析、测试工具,建议做法与禁忌。作为后来者当然不会舍弃这些基础设施而另起炉灶从零开始,于是使用类C语言就成了传统。可以预见:下一代的用户终端与的程序库接口,也是类C的编程语言。

我们回到MFC的话题。MFC只是对Windows SDK的封装,按后世的看法,MFC不按“套路”出牌!包括作为同时代竞争产品的Borland公司的VCL,还有稍晚的Java awt,也是不按“套路”出招!这里我需要解释下图形库构建的“套路”是什么,读者才能理解什么叫“不走套路”。

对于一个图形界面的程序,大致可以分为3个层:

+----------------------+

| user application |

+----------------------+

| ui framework |

+----------------------+

| operation system api |

+----------------------+

最下面的是操作系统API,无论是UI库还是用户程序,要实现某功能最终都是要依赖于操作系统API的。关键点在于,操作系统需要提供多少数量的API函数,才足以构建一个图形界面程序。答案是:很少。

首先,操作系统需要为UI框架和用户程序提供一块屏幕区域:

struct window *window_create();

其次,这块区域需要能够接收用户事件(这里以鼠标点击为例):

enum mouse_event {
    mouse_event_down,
    mouse_event_move,
    mouse_event_up
};

typedef void (*mouse_handler)(struct  window *, mouse_event event, int x, int y);
void window_set_mouse_handler(struct window *, mouse_handler handler);

最后,UI框架与用户程序需要在这块区域绘制各种控件(下例是矩形):

struct context *context_get_form_window(struct window *);
void context_set_color(struct context *, int rgba);
void context_fill_rect(struct context *, int x, int y, int width, int height);

只要有了这3类API,一个图形界面程序就可以构建出来了,即使如今复杂的3D图像程序也是如此。这些作为基础API的函数数量很少,往大的说不到一百个,往小的说,十多个也能成事。庞大的UI框架,诸如Qt、Xamarin、WinForm,仅仅是以这几十个操作系统API函数作为自己的基础;对操作系统极小的依赖,也是可移植的保障;也是UI框架构建的“套路”。

所以说微软的MFC、Borland的VCL不走“套路”,因为它们都是对Windows SDK的封装,而不是选择少量Windows SDK核心函数并在其上构建自己的工作逻辑。因为Windows SDK使用繁琐,于是有了MFC,但MFC是对Windows SDK的直接封装,虽然提升了易用性,但终究要沿用Windows SDK所定义的工作流程。当然这种做法也是有好处的,例如工作量更小、用户可以更方便地直接调用系统API。

说穿了,MFC(当然也包括VCL)需要解决的是“有无”的问题。Windows 1.0这样的图形界面操作系统已经开始在大众领域普及,开发者也需要一套框架来开发图形界面程序。MFC在上世纪90年代取得了巨大的商业成功,但以后世程序员的眼光看,MFC还是太稚气了。我曾经接触过一些学习Windows程序开发的新人,他们对MFC充满鄙夷,认为MFC是上个世纪的遗留产物,它概念多、晦涩难懂、代码量大、界面也丑,他们更倾向于Qt、WPF这样的新秀。他们的结论是对的,MFC确实是上个世纪的遗留产物;但推导过程不对,MFC与Qt、WPF本质上是一样的东西,在MFC上遇到的问题,在使用Qt、WPF时极可能也会遇到,不过是早还是晚。只能说“MFC对新人极不友好”。

在MFC同时代,也正是上世纪80、90年代,出现了对后世UI框架设计有极大影响的技术方案:用web浏览器作为图形界面程序的容器。由于篇幅的限制,我这里直接说结论:作为一个整体性的技术方案,它失败了。失败的原因可以大致归为3点:

  • 其一是观念上的落后。基于html、js、css的网页技术,最初的设计意图是解决文档浏览的问题,采用html为主,js、css相辅的设计方式。这样的设计在界面表现上有优势,但也仅此而已,其它方面诸如程序生命周期控制、数据处理等就太糟糕了。
  • 其二是基础设施的不如意。相关的人才与技术储备太欠缺,所能实现的产出物,无论在功能上还是性能上都无法与MFC、VCL这样的传统程序相同并论。
  • 其三是技术开拓者的急功近利。当时热衷于以浏览器作为图形程序容器的技术方案的厂商,如网景,还有后来的google和facebook,它们的意图很明显,就是挑战微软的平台霸权,建立有利于自身的生态环境(说人话就是:招揽开发者、提供生产工具、生产特定的程序、平台吃流量)。充满鼓动性的价值观营销在具有叛逆属性的年轻人中可以快速取得影响力,但得不到主流的支持和强势技术团队的示范,一切都是空。

尽管在新世纪的第二个10年以web前端为技术基础的UI框架,如electron、reactive native,取得了喜人的成果,但与上世纪的雏形相比,它们所在的大环境与技术本质已有了根本的区别。

但失败的方案并不代表没有借鉴意义。本世纪初出现了一种被称为“direct ui”的思潮,其中一大观点就是程序启动时加载xml文件作为UI。眼亮的同仁一定会说:这不就是资源编辑器莫。对的,这就是现代意义的资源编辑器的雏形。另外也多有程序会在自身嵌入一个web容器来显示特定内容的UI,至今Android、iOS App上这样的做法也相当常见。出于篇幅的限制这里的技术因果就不展开了。

上世纪90年代末到本世纪初是UI库百花齐放的年代,如今在PC领域,为人熟知的Qt、Gtk、wxWidgets都出现在这个时间段,这是与大环境有关。

  • 首先MFC解决了“有无”的问题,接下来就是“好用难用”的问题;
  • 其次由于前一个10年图形系统的普及,业界在图形程序的开发上有了大量的人才、技术、经验上的积累,开发一套UI库对于小团队来说也是敢想敢干的事;
  • 再次定制化的需求开始出现,是需要能实现快速开发的程序框架(WTL),还是可以实现跨平台的程序框架(Qt)?是需要能实现常见功能的中小程序框架(wxWidgets),还是得安装几个G的包但可以实现复杂应用的程序框架(WPF)?不同的团队面对不同的任务,一定会有不同的选择。
  • 此外当时微软作为处于垄断地位但又不思进取的操作系统厂商,也有推波助澜的作用。本世纪第一个10年,MFC已很老旧,但微软却一直没有给出升级方案;微软一味将开发者向.Net上推,但迁移成本太高,并且后者的性能问题也迟迟不能解决。于是开发者只能自己想办法。

Qt是传统PC领域程序框架集大成者,除了UI,它还提供数据库、多媒体等功能,已经不是单纯的UI框架,而是一套完整的程序开发解决方案;wxWidgets就要轻量级一些;WPF则是微软官方作为取代曾经的MFC、后来的WinForm的替代方案。

我这里谈一下跨平台开发的一些经验。问:使用跨平台的程序框架开发程序,相比于每个目标平台单独组建一个开发组,前者有更高的性价比?回答是:不一定。开发程序不仅仅是程序员的事,还涉及到产品策划、美术、市场运营、第三方技术供应商等方方面面,具体问题要具体分析。事实上资金与人力充足的团队,更倾向于不同目标平台组建各自的开发组。

新世纪的第二个10年,以iOS、Android主导的移动平台兴起。Qt推出了移动平台版本,但终究没有形成气候;微软推出UWP,WPF还是被舍弃了。这又是另一个话题了。

以上。共勉。

见解2

WTL都算不上什么Framework,就是利用泛型特性对Win API做了层封装,设计思路也没摆脱MFC的影响,实际上用泛型做UI Framework也只能算是一次行为艺术,这个思路下继续发展就会变得没法用了,比如 代码过于复杂,编译太慢,出错不好调试等问题难以解决。

而且封装得也不完全,还是随处可见 HWND HDC之类的东西。

用途主要是写一些很小的程序,或者作为其他UI框架的后端实现部分,比如我写过一个小框架用来做安装卸载程序,非常小,其中创建管理窗口部分是用WTL的。

MFC是更高级点的Win API封装,比WTL封装彻底,很难见到HWND HDC了,也提供了不少实用工具类,比如高级控件,泛型容器,IO访问,网络协议等。除此之外,还提供了一些基本框架,比如 Document/View,这就是个MVC的简化版本,只有MV,但是对于数据的管理,消息的传递等又没有什么约束,导致Doc/View被用得乱七八糟。尤其是对事件处理的模型,消息映射是功能简陋,而且容易出错的方式,唯一优点是性能好。 从VC++ 1.X就有MFC了,那时整个UI界的设计思想都比较落后(除了Apple),MFC又背负了沉重的兼容性包袱,比如vc++ 1.52的MFC程序到了vc2003稍加修改都可以编译,导致MFC后期没有什么发展,就是沿着老的思路完善了些细节,添加了些组件,但是根本性的设计问题没有改进。

GTK,这个吃了语言的亏,用C写面向对象实在是痛苦,虽然在思想上比MFC要先进了些,但是写出来的代码比MFC要罗嗦很多了。相比MFC,多了Layout的概念,事件处理上有了Signal/slot,虽然用起来很麻烦。

wxWidgets,这个基本就是个跨平台的MFC,对各个平台的差异做了抽象,实际上后端大多还是用平台原生的API实现,好多控件都是直接用系统原生的。有wxWidgets for GTK+的版本,后端就是GTK+,wxWidgets就是一层壳。这也是wxWidgets的优点,它编译出来的程序发行包比较小,性能也不错。

以上这些就是上世纪90年代的UI Framework技术水平了,至今它们也依然没有太多进步。

下面来谈谈21世纪的技术。

Qt,虽然它也是上世纪90年代出现的,但是它在21世纪有了长足的进步。应该说它的起点就比较高,一开始就定位跨平台,而且不满足于简单封装系统API,而是要自己创造出一套完整的API和框架,甚至要代替系统API,所以不仅仅是做UI,而是涉及到了APP开发所用到的所有东西,包括网络,数据库,多媒体,脚本引擎等。signal/slot是Qt发明的,这是事件通知模型里C++语言的最佳实现了,甚至我都觉得这该写进C++标准,估计C++委员会的老顽固们是从不写GUI的。

早期的QT也是没有DirectUI的概念的,每一个QWidget都对应一个原生窗口,从Qt4.4开始,只有顶层QWidget才是原生窗口,而Child Widget是Alien Widget,只是个抽象的图层不对应原生窗口,这就实现了DirectUI的概念,很多图形效果也就变得可能了,比如窗口层叠透明效果。

在4.8后实现了QPA(Qt Platform Abstraction),这就使移植Qt变得很容易,目前Qt是支持平台最多的框架没有之一。

由于早期授权的问题,Qt对于开源社区不是很友好,导致推广不太顺利,直到它改成了LGPL方式,如果Qt能早点想开了,恐怕就没有wxWidgets的生存空间了。

Qt的缺点也是有的,就是太大,不过可以自己剪裁,我可以把QT库剪裁到发行包压缩后2.5MB。

WPF,微软在Win Form的思路上走到死胡同后,终于痛下决心用正确的方法开发UI库了。21世纪的UI一定是定义出来的,绝对不能是代码写出来的,所以有了XAML这个强大的定义工具,不但可以定义UI布局,还包括图形动画效果,消息响应方式等。配合C#这种优秀的语言,更是如虎添翼。但是问题也很明显,就是过于庞大,不仅开发时要用到庞大的IDE和设计工具,发行的安装包也十分巨大,所以目前还是很少有人拿他写通用软件客户端的,大多是做企业项目时写专用客户端。

大概4-5年前吧疼讯曾经用WPF写了个QQ,但是只实现了基本功能就已经比C++客户端大好多了,而且运行缓慢,主要是太吃内存,而且那时WPF的优化还不充分。

最后我想补充下真正的UI库之王,cocoa。

Apple的成功有很多原因,其中之一就是cocoa,cocoa理念十分先进,而且出来得早,我都怀疑Qt和WPF有不少思想都是借鉴cocoa的。

定义式的UI,用xib就可以定义UI的绝大部分细节,而且提供所见即所得的可视化设计工具。

严格的MVC,而且定义非常清晰,分工明确。

signal/slot,虽然不叫这个名字,但思想就是,而且真的是拖动鼠标就能connect。

提供了ARC,闭包和反射,给UI开发带来巨大的便利性,当然这得益于Objective-C这个语言。

再补充下 Borland的OWL和VCL。

我是从Borland C++3.0和Delphi 1.0开始用的,那时的Borland看来很有前途的,可惜后来一系列决策失误导致现在这个公司几乎消失了,同学们不要再往这个坑里跳了。

OWL曾经和MFC是竞争对手,设计思想也差不多,个人感觉OWL的API设计更优雅一点,但是在市场上OWL被MFC彻底击败。

Delphi是神作,它在RAD(快速应用开发)领域长时间没有对手,直到BS架构取代CS架构。Delphi的特点就是简单、开发快,单纯就写个基本可用的应用来说,可能至今都没有比他更快的,但是缺点就是丑,基本大多数Delphi应用都是一大堆控件堆积在一起,很不美观,另外由于Pascal语言的限制无法和现有大量的C/C++代码融合。虽然后来有C++ Builder,但是Builder里简单和快的优点也消失了。Borland的C++编译器越做越差,导致后来开源项目都不太愿意兼容这个编译器了。

VCL准确地说不是UI库,而是一套组件接口规范,类似COM ActiveX。delphi和C++builder都是基于这个规范构建了基础库。

UI库是个很大的话题,够写好几本书来探讨的,我这里就是随便写点自己的感受。

单纯讨论每个库的优劣是没有意义的,而是要放到具体的应用场景里来看,每个库都有自己擅长的场景。

如果仅在Windows下,追求程序小巧,用WTL,不足的地方自己实现去吧,但是视觉效果就呵呵了。

如果可以大一点,还要好看点,那就Qt。

如果完全不在乎大小,只要视觉效果华丽,就用WPF,如果把开发工具价格也考虑进来,那么土豪才会选WPF呢。

MFC就是个鸡肋了,除非你现有的工程师不会用别的,或者有历史遗留代码要保持兼容。

如果要求跨平台,那么就用Qt,wxWidgets和GTK+跟现在的Qt比起来没有什么优势了。

如果是iOS Android,那么最好用原生UI库,除非你写游戏

见解3

08 年的时候,我刚听说微软出了 WPF,非常兴奋。那时候的我写的最多的是 WinForms 的应用程序,但我对 WinForms 程序有诸多不满,主要原因是 WinForms 的设计仅仅是对系统的 Win32 API 做了一层薄薄的封装,UI 设计方面束缚依然比较大——老老实实套用内置控件倒还好,一旦要做些许“创新”,开发复杂度就陡然增大。

于是当年我就赶紧买下了 Charles Petzold 老爷子写的《Windows Presentation Foundation 程序设计指南》,如获至宝的学起来。这期间逐渐感受到 WPF 的强大,灵活,并憧憬着用它开发出更加酷炫的应用程序。

然而现实是残酷的,一个棘手的问题是 WPF 性能比预期差很多。当你为一个控件添加下拉阴影特效(DropShadowEffect)后,再让其参与到动画事件中,性能会急剧下降。起先我以为是我使用方法不正确,但最终发现这是一个普遍问题。归根结底,DropShadowEffect 在当时是用 CPU 完成计算的,当存在动画时,为了保证渲染正确,需要逐帧重新计算,导致 CPU 占用率暴涨,性能自然受到拖累。

尽管我们可以尝试一些“优化”手段,例如预先计算出一次 DropShadowEffect 后将其缓存,后续仅以位图方式重绘,这一方案自然能快很多,却也存在这样或那样的弊端,例如需要考虑缓存结果是否能与背景正确匹配的问题。另一种办法,我们还可以在纯色背景上预先计算出 DropShadowEffect 后,再将半透明化,这样既能保证性能,又能保证与背景正确匹配。但还是略有瑕疵。总之方法虽多,但需要具体情况具体设计,非常不方便。说到底这个问题还是应当由微软解决,而不是推给框架的使用者。

作为 UI 框架新贵,WPF 存在些许缺陷不足为奇。但有趣的是,下拉阴影导致性能下降的问题在同期的浏览器(如 Firefox)中却并不存在,你可以为任何元素添加下拉阴影,然后让它们动起来,浏览器依然顺畅如初。这一个细节让当年轻视 Web 开发的我逐渐的意识到,浏览器作为一个渲染引擎,其性能经过了精心的优化,是一个优秀的 UI 容器。

受此启发,在近两三年里,我便很少再用 WPF 开发了。后续的项目都采用基于内嵌浏览器内核(例如XULRunner、CEF)的方式。这样一来 UI 都用 Web 来做,就拥有极大的灵活性,例如可以使用 ExtJS、Bootstrap、Semantic UI 框架等等,而应用程序的能力则依然是 Native 的,可以完成任何之前的程序所能做到的事(调用 Win32 API?很容易)

我们交付的许多项目都是这样实现的。开发成本大大下降,而且跨平台也不再是问题。关键是可以享受到 Web 开发后续发展的所有红利,例如模板引擎,响应式界面,React UI 等等。

近来,node.js 的出现也再一次降低了这方面的开发成本,特别是 node-webkit (近来改名为 nw.js),electron 之类的程序,更是将这方面的开发门槛急剧降低——之前你如果不熟悉底层开发,你可能就不知道应当如何将 CEF 之类的控件嵌入到你的程序界面里,另外还需要解决 Web 界面与底层库的通信问题。但是使用 node-webkit(或 electron) 根本无需考虑这方面的问题。对于需要调用底层 API 的时候,直接给 node.js 增加 C++ Addon,在界面里就可以通过 JavaScript 调用。非常方便快捷。而且还有大量的 package 可用。不能更强了。

但是底层开发人员在向 Web 技术拓展的时候,常常遇到的最大的问题是在于固有思维定势带来的偏见。其中最常见的问题就是,觉得 Web 很糟糕,没有 Control 的概念,缺乏封装。因此用起来总觉得不好用。起初我也是这样认为的,但后来我发现并非如此。这其实是一个设计理念的差异。慢慢会发现各有利弊。Web 基于 Document 的设计也有颇多精彩之处。

总而言之,我总希望大家在传统 Native 框架的基础上,多试试基于 Web 的方案。你一定会因此变得更加强大,并找到更多乐趣的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值