现在如何在 Mac OS X 中写一套输入法 (一)

[zonble](http://zonble.twbbs.org/) 曾经有一篇系列文章 ([1](http://zonble.twbbs.org/archives/2005_07/789.php), [2](http://zonble.twbbs.org/archives/2005_07/790.php), [3](http://zonble.twbbs.org/archives/2005_07/791.php)),比较简略的介绍了如何在 Mac OS X 下如何开发一个输入法,其中有一部分解释不足的地方,我希望在本文补足,如果你还没有读过 zonble 的原文,请先读读。

第一部分,关于资源文件。

我们都知道,Resource Fork 是 Mac OS Classic 时代的残余,通过 Resource 来作程序的定制 (比如 l10n) 也是在 Classic 时代推荐的,在 Modern Carbon 与 Cocoa 已经被 .nib 文件替代了,然而,Mac OS 的系统 Component 的编写直到现在仍然未有变化,仍然需要 Component 程序有一个 Resource (.rsrc) 文件,Component Manager 利用这个 Resource 文件了解 Component 的名字、入口点和图标等信息。

然而在现在的 Mac OS X 中的几个改进值得注意:

首先,[TN2012](http://developer.apple.com/technotes/tn/tn2012.html#TNTAG83) 指出,虽然原本 Component Manager 支持你将 Component 编译为一个 .dylib,将 .rsrc 作为它的 resource fork 使用,但对于 Mach-O Component 这个方法已经不_推荐_了,建议以 Bundle 的形式来存放,也就是将 .rsrc 文件存放在 .component/Contents/Resources/ 目录下。

顺便说一句,也许有人会把 Components 和 Bundles 的概念混淆,其实两者的差异是很大的,Component 是系统启动/用户重新 Login 时就会载入的代码,放在 /Library/Components (或者 /System/Library/Components 和 ~/Library/Components) 下的 .component 文件都会被尝试载入。如果你不 Logout/Login,那这些代码_不会_被重新载入。

而 [Bundle](http://developer.apple.com/documentation/CoreFoundation/Conceptual/CFBundles/CFBundles.html) 只不过是一种用目录来组织文件的形式,通过放置 PkgInfo 和 Info.plist 文件,使系统从外在把这个目录当作整个一个文件来处理。

所以 Component 和 Bundle 其实是两个正交的概念:Component 不一定要用 Bundle 来存储,Bundle 存储的也不一定是 Component (有可能是 .app, .framework)。

其次,TN2012 同样提到,Mach-O 格式的 Component 是唯一支持 Intel 架构的二进制格式,老旧的 CFM 格式 Component 已经不_应该_再使用。所以我们应该修改 .rsrc 的源格式 .r 文件,使之支持 Intel 架构。

在介绍怎么修改之前,先扯开一点,说说 .r 和 .rsrc。和 Windows 一样,资源文件是要编译以后才能使用的,源文件格式称为 Rez Input Format,而目标文件格式显然称为 Rez Output Format。用来编译和反编译的分别是 /Developer/Tools 目录下的 Rez 和 DeRez 两个命令行程序。

显然,我们需要关心的只是源格式。然而这个源格式非常的灵活,因为不只是 Component 要用到 resource,其他的东西也要用到,比如应用程序的 menu 信息,就曾经是用 resource 来指定的 (和 Win32 编程一致)。

资源有两个需要注意的概念,分别是 Resource Type 和 Resource ID。在 .r 文件中,我们通常这样写:

resource ‘abcd’ (123)
{
… 资源的内容
}

resource ‘efgh’ (456)
{
… 资源的内容
}

其中 `’abcd’` 和 `’efgh’` 就是 Resource Type,通常这四个字符是 Apple 预定义的,虽然也可以随便起,但 Component Manager 只会寻找它所认识的资源类型:`’thng’` (是 thing 的缩写,不知道为什么起这个名字)。而 123 和 456 是 Resource ID,其实只要你在这个 .r 里定义的同一个 Resource Type 的 Resource ID 不重复就行了,所以不同类型的资源其实可以用同一个 ID,后面我们会看到,BIM.r 就是如此的。

而中间省略的部分,资源的内容,根据不同的 Resource Type 而定,在 [TN1004](http://developer.apple.com/technotes/tn/tn1004.html) 中,给出了 `’thng’` 的内容格式。我们会在下面逐一解释。

关于 Resource 的资料,可以参考 Inside Macintosh 的 [Resource Manager](http://developer.apple.com/documentation/macos8/Files/ResourceManager/resourcemanager.html) 部分。

下面是我们解释 BasicInputMethod 的资源文件 BIM.r:

#define UseExtendedThingResource 1
#include
#include “BIMScript.h”

首先,必须在包含 Carbon 的头文件之前定义 UseExtendedThingResource,否则,Component Manager 就不支持扩展的 thng 格式,这是 m68k 到 Power PC 转换时出现的变化,在 Inside Macintosh 的 Component Manager 一章里没有介绍,但 TN1004 里解释了。包含 BIMScript.h 这个文件是为了引用一些常量 (资源文件可以引用头文件,这一点和 Win32 也是一样的)。

resource ‘thng’ (128)
{
‘tsvc’,
‘inpm’,
‘appl’,
0x8000 + kBIMScript * 0x100 +
kBIMLanguage,

到了 thng 部分,Resource ID 128 其实是随便起的。前四个项目 zonble 的文章都解释了,注意在 [TN2128](http://developer.apple.com/technotes/tn2005/tn2128.html#TNTAG4) 还介绍了如何创建 Palette 类型的输入法的方法,第一步就是要将这里的第二项 `’inpm’` 改为 `’cplt’`。而第四项,Component 的 Flags 是你的 International 设置里看到的所有输入法时右侧的 Script 一栏的基础。需要注意,如果你希望写一个通用的输入法框架 (类似 SCIM 或 OpenVanilla),像下面这样在 BIMScript.h 里定义常量会比较合适:

#define kBIMScript smUnicodeScript
#define kBIMLanguage langUnspecified

其中 script 的定义可以让你在 International 设置里看到 Script 是 Unicode,language 的定义的用途我还不太清楚,欢迎补充。

关于可以定义的 script 和 language 常量,可以看 [Script Manager](http://developer.apple.com/documentation/Carbon/Reference/Script_Manager/index.html) 的参考手册,它的 API 虽然几乎全部 deprecated 了,但定义的常量还是有用的。

kAnyComponentFlagsMask,
‘dlle’, kBaseResourceID,
‘STR ‘, kBaseResourceID,
‘STR ‘, kBaseResourceID + 1,
‘ICON’, kBaseResourceID,

下面定义了几个重要的参数:Component 的入口点函数应该在 `’dlle’` 资源里找,Component 的名称字符串,应该在 ID 为 kBaseResourceID 的 `’STR ‘` 资源里找,Component 的信息字符串,必须在 kBaseResourceID + 1 的 `’STR ‘` 资源里找,如果你看完了上面关于资源的解释,这就应该很好理解了。让我们看看实际这几个资源的定义:

resource ‘dlle’ (kBaseResourceID) {
“BIMComponentDispatch”
};

resource ‘STR ‘ (kBaseResourceID)
{
“Basic Input Method”
};

resource ‘STR ‘ (kBaseResourceID + 1)
{
“A Really Cool Input Method”
};

就一目了然了,入口点是一个叫 `BIMComponentDispatch` 的函数,名称是 `”Basic Input Method”`,信息是 `”A Really Cool Input Method”`。

回来继续看 ‘thng’ 资源的内容:

#if UseExtendedThingResource
0x00010000,
componentHasMultiplePlatforms,
kBaseResourceID
{
0x8000 + kBIMScript * 0x100 +
kBIMLanguage,
‘dlle’,
kBaseResourceID,
platformIA32NativeEntryPoint,
}
#endif

首先,我们注意到,这部分是 Power PC 开始扩展的内容,所以应当用条件编译括起来,然后是这个 Component 的版本,这里是 1.0。第 3 项是 Icon Family 的资源 ID,什么是 Icon Family 呢?在 OS Classic 里出现的这个概念是指,你可以把一堆 Icon 组织为一个 Family,分别是不同大小、色深的图标,当载入资源时,你只需要指定载入哪个 Family 的 ICON,就可以自动根据你要用到 Icon 的位置,判断出应该用这个 Family 里的具体哪个图标了。Basic Input Method 的 Icon 原来真的是在这里定义的,不过正如 zonble 原文提到,我们应该只能看到 `’kcs8’` 这个图标了,这是一个 32×32 的 256 色图标。

不过,现在我们不必使用 zonble 给出的修改资源文件的方法来修改图标了,[TN2128](http://developer.apple.com/technotes/tn2005/tn2128.html#TNTAG6) 中解释,通过资源文件来设置图标的方法已经不建议再用,你可以通过在 Component 的 Info.plist 中指定 `tsInputMethodIconFileKey` 一项来定义图标。你可以参考 QIM 的作法,修改 Xcode 项目的 Products/BasicInputMethod.component/Contents/Info.plist 文件,添加下面几句:

CFBundleIconFile
Icon.icns
tsInputMethodIconFileKey
Icon.icns

其中 Icon.icns 就是 .component/Contents/Resources 下放置的一个图标文件,支持 .tiff 和 .icns。

接下来的 { } 中部分是一个专门描述模块的 Platform Info 的部分,其 4 项依次是 Component Flags,’dlle’ 和 ‘dlle’ 资源的 ID,最后一部分最重要,描述编译在哪个平台下执行,这里指定 IA32,就是只给 Intel Macs 用,你也可以参照 TN2012 来编译 Universal Binary 的 Resource。

至此,资源文件的介绍就到这里。使用官方下载的 BasicInputMethod 的项目加上我这里附上的 [BIM.r](http://jjgod.org/code/BIM.r),应当可以编译出一个放在 /Library/Components/ 下便能顺利执行的输入法模块来,希望这篇介绍能帮助更多的朋友开发开放源代码的 OS X 输入法。

找到一个不错的 Linux 发行版

最近因为开发内核程序的原因,需要找一个很小的发行版,放在 qemu 虚拟机里测试。

找了找,发现 [Core GNU/Linux](http://www.coredistro.org/) 很不错,光盘启动安装,把硬盘挂载上,分区,执行安装脚本,chroot,装上 grub 就搞定了,总共也只用 200M 左右的空间,主要软件都是 bleeding-edge 的,很方便。

管理大型的 Cocoa 项目

NetNewsWire 的作者 Brent Simmons 最近的一篇文章介绍了[如何有效的管理大型 Cocoa 项目](http://inessential.com/2007/04/25.php)。他的建议可以总结为下面几条:

为了改善代码的可读,可查找性,应该遵循:

1. 只对那些没有明显关系的对象之间的交互使用 Notification。
2. Key-Value Observing 也是很危险的,应该只对 Preferences 项目使用这一特性。
3. 只将 Binding 用于很简单的情形,复杂的 TableView 还是用 datasource/delegate 比较好。

管理代码时可以使用的技巧:

1. 用 `#pragma mark` 来划分代码的区域
2. 用 Ctrl-2 来列出当前打开文件的符号 outline。
3. 用 Shift-cmd-D 来快速打开指定文件。
4. 用 opt-cmd-T 来将当前打开的文件和左侧的目录树同步。
5. ctrl+double click 打开符号的定义,opt+double click 打开符号的文档。
6. 在文件系统中用平面方式组织文件,不划分多层目录,在 Xcode project 中用 group 来划分层级结构。

对于有 Cocoa 开发经验的人,尤其是管理过这种超过 200 个源文件的较大项目的人来说,这些经验是很有用的。