从Windows角度看Mac OS X上的软件开发

时间:2024-01-16 11:56:50

如果原来从事Windows软件开发,想跨足或转换至Mac OS X环境,需要知道那些东西?有什么知识技能可以快速运用在Mac OS X环境上的?这两个问题应该是Windows开发者进入Mac OS X环境最关心的问题。本文假设读者以往采用微软的开发工具,并以C/C++/C#的任一种组合作为开发语言。

大体说来,Windows和Mac OS X都是为桌面应用环境、图形用户接口(GUI)而设计的操作系统。虽然不同平台细节各有特色,但两者相近的抽象概念,其实远远多于相左之处。本文试图指出方向上明显的异同所在,而非详细列举各种细项差别。最后,我也将简短分享自己在开发跨平台软件时的一些技巧和心得。

系统架构与开发环境的差异

用最简单的话来说,Mac OS X与Windows在架构与开发环境上最大的不同点在于:OS X是UNIX也不是UNIX;OS X主要开发工具Xcode使用GCC作为编译程序,与其他种类的UNIX相同;不过OS X也有独树一格的"bundle"软件包装格式这样的东西,成为它与其他操作系统不同之处。

Windows和OS X都属于现代的操作系统,所以Windows在操作系统层级所提供的功能──执行文件与链接库加载、多任务与多线程、内存管理──在OS X上都找得到对等的API和作法。不过,相较于Windows在微软独力开发下,架构和API都维持着相对的一贯性(另一方面,也背负着各种历史遗迹和向下相容的包袱),Mac OS X则是底层源自NeXTSTEP的Mach微核心(现在称为XNU),而应用层(用准确的UNIX术语来说叫userland)来自FreeBSD 4。这件事情相当重要:OS X透过这样的架构,才拥有和一般Linux/FreeBSD相似的UNIX应用环境。有相当多Mac软件开发者喜欢在UNIX
shell下工作,使用各种UNIX工具。在Windows上,必须加装Cygwin之类的环境才能办到。

Apple几年前有则广告是「把其他牌子的UNIX送进/dev/null里」(用过UNIX的朋友应该不难体会其中的吹嘘意涵)。平心而论,OS
X受益自UNIX环境之处不少。尤其,Apple使用了大量的open source工具。举例来说,Apple不像微软,没有自己的C语言编译工具,Apple用的是UNIX业界的标准──open source的GCC(其中当然有不少OS X的扩展功能就是)。虽然Apple有自己的开发环境Xcode,但是底层采用GCC这件事对开发者来说是相当重要的。同时,Apple的C/C++链接库用的也是GCC标准的stdc/stdc++。了解这个差异,在遇到与Microsoft C/C++ compiler不同的地方时,就更容易能找到解答的资源(这类型问题往往不限于OS
X,其他UNIX平台也会发现)。

但是Mac OS X并不完全是UNIX。它的GUI环境(Aqua)就完全不是一般Linux/FreeBSD所使用的X11。而在UNIX层之下的微核心也和其他UNIX不同。接下来这一点很重要:OS X虽然有和Windows .EXE和.DLL相对应的文件(OS X跟其他UNIX一样,可执行文件一般不加扩展名,UNIX系的动态加载链接库则冠以.dylib),但更重要的架构差异是bundle。

Bundle概念承袭自NeXTSTEP。简单来说,就是由操作系统提供一种类似对象封装的文件包裹。OS X上最常见的bundle要属.app结尾的应用程序了。虽然.app外观上是个文件,在UNIX shell下看就能发现它其实是个目录,内含各种metadata(通常至少会有一个名为Info.plist的数据文件)、可执行文件、动态链接模块、各种资源等。除了.app外,OS X的各种框架档(以.framework结尾,是一种同时包含头文件及链接库的包装)、应用程序的外挂模块(通常以.bundle结尾)等等,都是以bundle形式呈现的。了解这个差异,才能了解为什么OS
X上很少有程序需要额外的安装程序,也鲜少听说有所谓的"DLL hell"(因共享链接库版本不兼容造成的困扰)。

项目 Windows Mac OS X

操作系统最近桌面版本 Windows Vista Mac OS X 10.5 Leopard

操作系统核心 NT Kernel XNU

CLI Shell环境 CMD.EXE UNIX shell (bash/tcsh/etc., 可使用Terminal.app一类的终端机软件进入)

GUI (Shell) 环境 Windows Explorer Aqua (Finder)

程序二进制文件格式 Portable Executable (PE): .EXE, .DLL Mach-O "universal" binary (可执行文件通常不带附加名,DLL结尾为.dylib)

用来辨认软件组件的方式 GUID bundle identifier (Java式的id,例如com.apple.TextEdit)

厂商提供或贩卖的开发环境 Microsoft Visual Studio Xcode

可视化的GUI制作工具 Visual Studio内建的WinForm designer Interface Builder

C编译程序 Microsoft C Compiler GCC

表一:Windows与Mac OS X在架构上的对照

开发语言与API;Objecitve-C, Core API, Carbon, Cocoa

如果使用微软工具来开发Windows软件,就一定会碰到Platform SDK,MFC或者.Net平台,同时,也相对应到C、C++、C#和其他.Net平台所提供的语言(这种区分并不是绝对的,仅仅是为了方便接下来的模拟所做的简化)。在OS X上,Apple则是鼓励大家尽量采用Objective-C作为开发语言,并且熟悉Cocoa。

接下来的问题既尴尬又麻烦。很多人会问:我们是否非学Objective-C不可?另外一个常见的问题是:Apple不是也有名叫Carbon的C API吗?(延伸出来的问题则是:可不可以用C++开发Mac程序?)。

简单的答案(同时一定程度上也代表Apple的态度)是:要用Objective-C才能完全发挥OS X图形应用环境的长处,而Cocoa这个用Objective-C写成的API framework就是最佳的施力点。

复杂的答案则是这样:

OS X的本体,也就是所有非UNIX的部份,并不像Windows一开始就(几乎)全以C写成的。因此OS X没有所谓"Win32 API"这么纯粹的东西。OS X核心的、非GUI的服务和链接库,有时称为"Core API"。Core API大部分以C写成,并且多半奠基于CoreFoundation这套链接库之上。CoreFoundation提供了一贯的内存管理模式(CFRetain, CFRelease)、基础的数据型别(字符串、数组、字典)、property list文件管理、文件、网络存取等等。CoreFoundation使用上跟Win32
API有点相似,都透过存取handle的方式来达到某种近似「用C语言操作对象」的效果。但CoreFoundation最大的不同在于它还有reference counting的内存管理模式,大幅简化了内存管理的复杂性。

至于Carbon,严格说来,是Mac OS X在发行之初,为了维持与Mac OS 9兼容,才提供一套以C写成的GUI工具集,主要包括所有的GUI组件(Apple 称为 HIToolbox ,HI 意思是 Human Interface)以及所有OS X之前的API(QuickDraw等等)。随着OS X 10.5的推出,Apple渐渐舍弃了旧式的API ,鼓励大家使用Objective-C写成的Cocoa来开发程序。Carbon现在的意义等于就是HIToolbox,也就是OS X GUI 的C API。

但是,Apple在2007年夏天做了重大的宣布;Carbon不会有64-bit的版本。也就是说这一套C API是「没有未来」的。这意味着所有使用Carbon写成的软件──Microsoft Office、Adobe Photoshop都不可能顺利过渡到64-bit。至于像QT这一类跨平台的GUI kit也势必要顺应这项改变。

其实Objective-C并不难学。由C转换到C++/C#时需要学习很多新观念、新用语,但Objective-C大体上只是在C语言上加上一层薄薄的、动态的面向对象层。Cocoa则是相当容易上手的API。透过Cocoa就可以用面向对象的方式存取OS X八成上的系统服务(其余两成可以用C来呼叫)。Objective-C可以跟C完全混用。同时Apple也提供了所谓的"Objective-C++",可以在C++程序中呼叫Objective-C程序,或者在Objective-C里撰写C++程序代码。Apple自家的浏览器Safari就有不少核心的程序代码(WebKit)使用了Objective-C++来撰写。

项目 Windows Mac OS X

主要开发语言 C/C++/C#(及其他.Net支持的语言,如C++/CLI) Objective-C/Objective-C++/C/C++

操作系统服务 Win32 API 系统服务多半可从POSIX layer用stdc/stdc++取用

系统核心服务 Win32 API CoreFoundation/CoreServices

绘图与GUI Win32 (GDI32, USER32) Quartz (C API)/HIToolBox (Carbon)/AppKit (Cocoa)

面向对象的API .NET Framework/MFC Cocoa

面向对象的GUI及绘图系统 WPF/GDI (with MFC) AppKit (Cocoa)以及Cocoa Graphics

桌面应用程序的数据库方案 ODBC/ADO.Net CoreData

基础绘图系统使用的单位 Pixel (GDI) Point (Quartz)

默认的屏幕分辨率 96 DPI 72 DPI

表二:开发语言与API的对照

图形作业环境的差异:绘图系统

大家对OS X最主要的印象,想必还是它的图形作业环境。GUI的确是OS X与Windows差异最多的地方。

在Windows环境里,传统上Win32 API同时包括了绘图(所谓的GDI/GDI+)和GUI组件(窗口、对话盒、按钮等等)的操作。到了.Net 3.0有所谓的WPF (Windows Presentation Foundation)。严格说来所有Windows上的概念和组件,都可以在OS X上找到相对应的作法。但是在架构上OS X确实和Windows有相当大的差异。

OS X的绘图系统核心是Quartz。Quartz的绘图基础概念是路径(path),而不是像素(pixel)。惊人的事实是:Quartz是一套PDF绘图系统。所有Quartz能绘制的对象都能轻易转换为PDF文件。至于在图像处理上,Quartz提供了一套完整的合成模型(compositing model)。简单地说,Quartz赋予了Mac OS X极为优异的绘图能力。从一些细节就可以看出Quartz在视觉上的细致度:例如,OS X在显示字型时的去锯齿(anti-aliasing)处理就要比Windows来得细腻,在点阵影像的缩放上效果也往往比Windows好。OS
X的应用程序可以轻易做出各种透明度的图层、以及为图形对象加上阴影、或者绘制不规则形状(但这并不代表你应该只是为了为了吹嘘而滥用这些功能,我们马上会提到用户体验这件事)。倒是有个细节应该马上一提,那就是Quartz的默认分辨率是72 DPI,所使用的单位是点(point),这跟PDF绘图系统是一致的,和Windows预设为96 DPI、以像素为单位的点阵式绘图系统很不一样。这在一开始可能很困扰人。因为在OS X上,不改变屏幕设定的情况下,12 pt的字,就真的会被会绘制成12 px(而在Windows上,12
pt却是16 px)。同时,Quartz默认的坐标系统跟数学上的习惯相同,也就是(0, 0)坐标起点是位于左下方,而不是一般计算机绘图使用的左上方(当然,Quartz有各种坐标变换功能,因此当然还是可以把(0, 0)设定为左上方的)。

看似复杂,然而,当你开始想输出PDF(打印作业大幅简化)或进行精细的绘图工作时,慢慢就会发现Quartz这样设计的直观了。

另外,Quartz的基础API是以C写成的,所有对象操作方式都跟CoreFoundation一样(从Quartz建立的对象都是用reference counting的方式在管理内存,同时也都可以用CFRelease来释放)。不过,Cocoa也提供了绝大多数的API对应。使用Objective-C来操作绘图对象会更轻松些。

在Quartz之上,或者与Quartz并行的,还有Apple的各种图形和媒体相关的子系统。诸如可以快速制作动画的Quartz Composer、新一代文字输出编排系统CoreText、应用层的2D动画系统CoreAnimation,以及Apple的招牌多媒体架构QuickTime,还有业界标准的OpenGL,这些构成了Mac OS X在视觉及媒体经验上的核心。

图形作业环境的差异:GUI,以及,用户体验

Windows上,尤其是Win32 API里面,绝大多数关于GUI的概念和技能,都可以直接转换到OS X上。OS X的GUI同样是采用事件驱动模型(event-driven model)来设计的,每个GUI应用程序同样都有所谓的run loop(或称event loop/message loop)。两者甚至在某些系统限制上也雷同:例如,.Net跟Cocoa都不鼓励或甚至禁止程序在主线程以外的地方创建或操作GUI对象。

尽管如此,GUI是造就Mac OS X在外观上与其他平台不同的最大要素。与之相伴的是OS X对于用户体验近乎执着的追求。

OS X在GUI上并没有一个特别的子系统。通常我们用接触到的API来区分。好比说如果用的是Carbon我们会称为HIToolkit,如果用的是Cocoa则会说是AppKit(Cocoa主要是由非GUI的Foundation──不要和CoreFoudation搞混了──以及提供GUI组件的AppKit所组成的)。Apple的开发工具中并没有类似Visual Basic一类把接口画完、在组件上点两下鼠标,把程序填进去就完成应用程序的工具或流程。最接近的是Interface Builder (IB)这套工具。IB做出来的.nib文件其实就是封存好的GUI对象,生成之后再回Xcode将必要的连结关系拉完,程序代码填上(通常量不会很多)就完成程序了。IB会是Xcode以外,OS
X开发者最常用的工具。

OS X提供的GUI组件特色为细腻、一致、直观。这并不代表OS X的GUI无法做复杂的设定和客制化。但是相较之下,OS X的应用程序更倾向于善用或组合现有的视觉元素,而较少自创新的custom control。这一点和Windows上,尤其是小型工具程序,喜欢一种程序就创造一种视觉风格,或是大量提供使用者可更换的skin,有着相当大的文化差异。虽然Apple自家的软件跟微软相似,喜欢提前使用下一个版本才出现的视觉风格或元素,有时让开发者觉得难以捉摸,但大体上遵守Apple自家的HIG (Human Interface
Guideline)还是常态。

我们提到了文化差异;OS X在视觉上的细腻,以及对用户体验的追求,造就了一种高要求的文化。这可以说是一种正向循环。我们或许很少听说哪个Windows开发者会为了icon向左偏了1 pixel而大改特改,或是要求自己的软件要在视觉及操作上符合哪个规范的一致性。但OS X的开发者真的会谈论并严肃看待这件事情(著名的icon设计商IconFactory以及独立软件商Panic是著名的两个代表),同样的也有相当多OS X使用者以同样严苛的标准看待他们使用的软件,甚至可能写信告诉你,指出你的软件在用户体验或视觉设计上的缺陷(笔者就曾经收到使用者来信,指出笔者的一个软件在pull-down
menu中使用的icon「语意」不合乎用户对该种GUI组件的期待)。又好比说,从OS X 10.5 Leopard开始,icon最大可以大到512x512,Apple也强烈建议开发者要准备这么大的尺寸(除了原有的16x16、32x32、128x128之外)。这当然无形中提高了开发的挑战。Windows在XP以前仅支持16x16、32x32、48x48,直到Vista才开始加大到64x64和256x256。

另一个与GUI不直接相关,但却影响用户体验的,是OS X的本地化(localization)系统。这一点也是和Windows不同的地方。OS X因为有bundle的设计,因此能让一个应用程序同时包装各种不同语系的资源文件,同时开发多语系程序在OS X上也相对容易(通常是以提供各种不同版本的.nib bundle放进应用程序bundle中Content/Resources/底下以语系区域来区分的子目录中就完成了。Windows程序设计一向以"resource file"概念来管理icon及本地化等「外部」资源,名称相似,开发方式却不那么一贯而直观;另外,OS
X的语系是可以按照顺序fallback的,例如要是繁体中文语系档找不到,而用户在语言设定中将简体中文设定在繁体中文的后头,那么OS X便会尝试套用简体中文语系档),结果是OS X使用者对本地化同样有着高标准与高期待。另一方面,笔者也建议大家,除非软件确定只有中文用户使用,不然一开始先以英文界面开发,再加上中文的本地化资源,以长期来说是值得(甚至是必要)的投资。

一些较难归类但同样重要的差别

Mac OS X跟Windows在软件开发作法上的差异还有很多,上述只就最大的方向差异阐释。有些较细微但值得一提的差别,我们也在这里简单说明。

首先,OS X跟Windows一样,内部字符串编码以Unicode为准。但在操作系统不同的层级,使用方式并不相同。Windows的Unicode layer很一致地使用了UTF-16作为编码,并偏好使用BOM辅助判别。OS X的文件系统使用UTF-8,而CoreFoundation及Cocoa则用UTF-16。如果使用Cocoa自己的serialization机制,Cocoa会正确储存和还原UTF-16的位顺序。不过,笔者自己建议,尽可能使用UTF-8作为各种交换时的编码(相对于Windows对于UTF-8的支持不够干脆简明,Cocoa自己就提供了像stringWithUTF8String以及UTF8String两种NSString的method,方便在native
string与UTF-8间的游走)。

其次,相对于Windows使用registry来管理应用程序设定,Mac OS X使用的是一种叫做property list(文件扩展名为.plist,简称plist)的XML文件。Plist可以直接变成CoreFoundation及Cocoa的各种容器对象,也可以将后者轻易地serialize成plist。因此OS X上的应用程序大量使用plist作为配置文件的格式,甚至作为数据单元格式。将设定用个别文件储存也减少了Windows集中管理registry所带来的各种弊病。

Mac OS X并不使用COM (Component Object Model)来作为面向对象的进程间通信(IPC; interprocess communication)的机制。因为用Cocoa写成的程序,可以透过Objective-C Distributed Object (DO)这个强大机制来达成IPC的任务。除此之外,因为bundle架构,OS X软件要设计外挂模块架构也相当容易。OS X有相当多支持外挂的应用程序,应归功于这种开发上的便利度。

OS X应用程序能够利用所有OS X在UNIX环境上所提供的功能。同时OS X一安装好就已经帮你准备好了大量的open source链接库,例如可用来制作密码密钥认证的OpenSSL、负责解压缩的libz、内嵌式数据库引擎SQLite等等。这些都是加速开发的好帮手。

最后要提的是,正因为OS X的文化与Windows有许多不同处,笔者建议跨足OS X的开发者应该要尽可能贴近甚至配合OS X的习惯。举例来说,大多数OS X应用程序都不需要安装程序,只需要直接将软件拷贝到想要存放的目录(通常是/Applications)即可。而解安装也就直接删除该.app bundle就解决了。在Windows上就没那么容易了(特别是有相当多组件依存关系的软件)。这些都是开发上需要注意的地方,但是开发者多付出一份心力,使用者就会多一份便利,终究会得到用户肯定的。

项目  Windows  Mac OS X 

系统内部编码 Unicode (UTF-16) Unicode (文件系统使用 UTF-8, 系统API一般使用 CFString/NSString, 内部使用UTF-16)

语系处理 区分Codepage 不区分Codepage

应用程序的设定管理方式 Windows registry Property list files

IPC的几种方式 COM/Windows RPC Objective-C Distributed Object/Apple Event/BSD Socket

脚本语言的支持 VBScript/JScript/CScript/DOS Batch script AppleScript/Perl/Ruby/Python/shell script

表三:一些重要的系统特性(摘录)

项目 Windows (.NET) Mac OS X (Cocoa)

字符串处理 System.String NSString 

数据结构与容器 System.Collections NSArray/NSDictionary/etc. 

HTTP网络存取 System.Net System.Net,NSURLConnection

XML解译 System.XML NSXMLDocument etc.

表四:几个代表性的.NET namespace/class在Cocoa中的对应class

跨平台的建议

最后简短分享一些跨平台软件开发所可能遇到的问题。

要同时在Windows和Mac上开发,有两种可能的思维方式。一种是追求真正的"write once, run everywhere"。此时开发的选择,可能是采用Java平台,Adobe的AIR,抑或使用C++搭配像QT这样的跨平台链接库。这三种主流方案各有千秋,但在视觉和用户体验上往往皆无法与原生(native)的Mac应用程序相比。

因此,另一个方向则是体认到,要保有Windows及Mac各自平台的特长,就必须割舍GUI跨平台的可能性。也就是说,GUI是最无法移植到其他平台的部分。我们能做的是将共通的逻辑部分独立出来,然后开发两套前端接口(frontend)。若以在Windows及Mac上皆能使用为前提,共通逻辑开发语言的选择就很少了,不是C就是C++。所幸Windows和Mac上具有平台特色的语言,要和C++结合,也不是那么困难的事(在.Net上是透过C++/CLI,在Mac上是透过Objective-C++这两种扩展的语言)。

不过,在开发共享部分的时候,最容易碰到的问题,恐怕还是要如何省下力气去做例如解译XML文件、存取网络这一类不是GUI的工作。这类工作的麻烦在于,Windows和Mac都各自提供了相当便利、但也绝对和平台相依的链接库(例如.Net的System.Xml,Cocoa的NSXMLDocument)。在这种情况下,我们也大体有两种选择:不是全部采用跨平台的链接库(例如使用expat来解译XML),就是善用面向对象的抽象化以及Abstract Factory这样的设计模式(design pattern),让程序逻辑呼叫抽象的接口,然后在于各自平台的版本中藉由呼叫平台相依的API来实现这些对象。

结论

本文简要地讨论了Windows及Mac OS X在操作系统架构、开发环境、API、图形环境等环节上的相近处与不同的地方,也简单提出了跨平台应用程序开发的两种策略。事实上在两种平台上开发所需要了解的概念跟技能没有太大的不同,两种平台在性能上的差异也不大,但是在实现细节、视觉表现与用户体验上,OS X有自身独特的风格与文化。OS X软件开发社群常常说要"be a good Mac citizen"意思也就在此。了解这些差异和独特性是撰写合宜的OS X软件的第一步。