[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 文件,添加下面几句:
其中 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 输入法。
什么时候写(二)啊?很期待,文章写的很详细,这样的资料在苹果开发资料中找不到。谢谢你做的补充。
这是国内的博客吗,作的真是棒,支持你