除了應用程式 (Application), GNUstep 還可產生程式庫, bundle 及工具 (command-line tool). 在這介紹製做這些功能的方法. 在這裡先撰寫一個常規表示的程式庫, 再寫個工具來使用這個程式庫.
程式庫其是就是類別的集合. 大般的 Unix 系統都有內建的常規表示程式庫, 在這裡只是用 Objective-C 加以包裝. 首先是 GNUmakefile.
GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make LIBRARY_NAME = libRegEx libRegEx_OBJC_FILES = RegEx.m libRegEx_HEADERS = RegEx.h # The Headers that are to be installed libRegEx_HEADER_FILES = RegEx.h libRegEx_HEADER_FILES_DIR = . libRegEx_HEADER_FILES_INSTALL_DIR = RegEx include $(GNUSTEP_MAKEFILES)/library.make
與應用程式的 GNUmakefile 大同小異. 這裡使用 LIBRARY_NAME 指定程式庫名稱. libRegEx_HEADER_FILES 是要安裝的檔頭 (header), libRegEx_HEADER_FILES_DIR 是這些檔頭所在的目錄, libRegEx_HEADER_FILES_INSTALL_DIR 是要安裝的目錄. 在這個範例中即是 GNUstep/Local/Library/Headers/RegEx/. 有關 GNUmakefile, 可參考 document for GNUstep Makefiles Package
接下來是程式庫的內容:
RegEx.h
#ifndef _RegEx_H_
#define _RegEx_H_
#include <Foundation/Foundation.h>
#include <regex.h>
@interface RegExPattern : NSObject
{
regex_t *preg;
/* Mask could be regex options. For example: REG_ICASE, REG_NEWLINE*/
unsigned int _mask;
}
+ (RegExPattern *) regexPattern: (NSString *) pattern;
- (id) initWithPattern: (NSString *) pattern options: (unsigned int) mask;
- (regex_t *) pattern;
@end
@interface RegExParser: NSObject
{
}
/* The return range is related to the whole string.
* Not related to the given range.
*/
+ (NSRange) rangeOfString:(NSString *) pattern
inString: (NSString *) string;
+ (NSRange) rangeOfPattern: (RegExPattern *) pattern
inString: (NSString *) string;
+ (NSRange) rangeOfString: (NSString *) pattern
inString: (NSString *) string
range: (NSRange) range;
+ (NSRange) rangeOfPattern: (RegExPattern *) pattern
inString: (NSString *) string
range: (NSRange) range;
@end
#endif /* _RegEx_H_ */
RegEx.m
#include "RegEx.h"
@implementation RegExPattern
+ (RegExPattern *) regexPattern: (NSString *) pattern
{
id object = [[RegExPattern alloc] initWithPattern: pattern
options: REG_EXTENDED];
return AUTORELEASE(object);
}
- (void) dealloc
{
regfree(preg);
free(preg); /* Not sure about this */
[super dealloc];
}
- (id) initWithPattern: (NSString *) pattern options: (unsigned int) mask
{
int result;
char errbuf[255];
_mask = mask;
preg = malloc(sizeof(regex_t));
result = regcomp(preg, [pattern cString], mask);
if (result != 0)
{
regerror(result, preg, errbuf, 255);
NSLog(@"RegEx Error: Couldn't compile regex %@: %s", pattern, errbuf);
regfree(preg);
return nil;
}
self = [super init];
return self;
}
- (regex_t *) pattern
{
return preg;
}
@end
static regmatch_t pmatch[1];
static char errbuf[255];
@implementation RegExParser
+ (NSRange) rangeOfString:(NSString *) pattern
inString: (NSString *) string
{
return [RegExParser rangeOfString: pattern
inString: string
range: NSMakeRange(0, [string length])];
}
+ (NSRange) rangeOfPattern: (RegExPattern *) pattern
inString: (NSString *) string
{
return [RegExParser rangeOfPattern: pattern
inString: string
range: NSMakeRange(0, [string length])];
}
+ (NSRange) rangeOfString: (NSString *) pattern
inString: (NSString *) string
range: (NSRange) range;
{
return [RegExParser rangeOfPattern: [RegExPattern regexPattern: pattern]
inString: string
range: range];
}
+ (NSRange) rangeOfPattern: (RegExPattern *) pattern
inString: (NSString *) string
range: (NSRange) range
{
int result;
int location, length;
int mask = 0;
/* Considering the situation of beginning line */
if (range.location != 0)
mask = mask | REG_NOTBOL;
if ((range.location + range.length) != [string length])
mask = mask | REG_NOTEOL;
result = regexec([pattern pattern],
[[string substringWithRange: range] cString],
1, pmatch, mask);
if (result != 0)
{
if (result != REG_NOMATCH)
{
regerror(result, [pattern pattern], errbuf, 255);
NSLog(@"RegEx Error: Couldn't match RegEx %s", errbuf);
}
return NSMakeRange(NSNotFound, 0);
}
location = range.location + pmatch->rm_so;
length = pmatch->rm_eo - pmatch->rm_so;
return NSMakeRange(location, length);
}
@end
這個程式庫的內容其實不重要. 編譯及安裝完之後便可以使用這個程式庫了.
在這裡寫個工具來測試這個程式庫. 工具名稱為 regex_test
GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make
TOOL_NAME = regex_test
regex_test_OBJC_FILES = \
main.m
regex_HEADERS =
ADDITIONAL_TOOL_LIBS += -lRegEx
include $(GNUSTEP_MAKEFILES)/tool.make
使用 TOOL_NAME 來指定工具名稱. ADDITIONAL_TOOL_LIBS 用來指定所需的 libRegEx 程式庫.
main.m
#include <Foundation/Foundation.h>
#include <RegEx/RegEx.h>
int main (int argc, const char **argv)
{
NSRange range;
NSAutoreleasePool *pool = [NSAutoreleasePool new];
range = [RegExParser rangeOfString: @"middle"
inString: @"head middle end"];
NSLog(@"%@", NSStringFromRange(range));
RELEASE(pool);
return 0;
}
GNUstep 的工具其實就是 Unix 的程式. 因此可直接在其所在的目錄執行, 或是設好路徑, 或是使用 opentool 這個 GNUstep 附帶的程式來執行工具, 以避免設定路徑的麻煩.
在這裡可以把程式庫及工具都放在同一個專案目錄中, 避免程式碼到處分散. 這時可以使用 GNUmakefile 的子專案的功能.
在這裡專案目錄假設為 ~/foo/. 工具的部份放在 ~/foo/ 中, 程式庫的部份則放在 ~/foo/RegEx/, 做為工具的子專案. 工具的 GNUmakefile 如下:
GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make SUBPROJECTS = RegEx TOOL_NAME = regex_test regex_test_OBJC_FILES = \ main.m regex_HEADERS = ADDITIONAL_TOOL_LIBS += -lRegEx ADDITIONAL_LIB_DIRS += -LRegEx/$(GNUSTEP_OBJ_DIR) include $(GNUSTEP_MAKEFILES)/aggregate.make include $(GNUSTEP_MAKEFILES)/tool.make
SUBPROJECTS 表示有個 RegEx 子專案目錄. ADDITIONAL_LIB_DIRS 則表示編譯好的程式庫所在, 如此工具便可以在程式庫未安裝時找到正確的程式庫. aggregate.make 指示 gmake 編譯子專案的方法.
現在所有的程式碼都在同一專案目錄 ~/foo/ 中. 程式碼在此:RegEx-1-src.tar.gz
除了程式庫, GNUstep 還支援 bundle. Bundle 算是可動態載入的程式庫, 或是 Plug-in. Bundle 可以在任意時間載入, 程式庫要在編譯時就指定好.
在這裡將程式庫改成 Bundle.
GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make BUNDLE_NAME = RegEx BUNDLE_EXTENSION = .bundle BUNDLE_INSTALL_DIR = $(GNUSTEP_INSTALLATION_DIR)/Library/Bundles RegEx_OBJC_FILES = RegEx.m RegEx_HEADERS = RegEx.h RegEx_PRINCIPAL_CLASS = RegExParser include $(GNUSTEP_MAKEFILES)/bundle.make
只是將 LIBRARY 換成 BUNDLE. 最重要的是 RegEx_PRINCIPAL_CLASS. 如此可以從 bundle 中取出最主要的類別而不需要知道該類別的名稱. 如此便完成了 bundle.
使用 bundle 的方法是找出其路徑, 載入, 取出特定名稱的類別或是主類別. 這裡是使用 bundle 的工具.
GNUmakefile
include $(GNUSTEP_MAKEFILES)/common.make SUBPROJECTS = RegEx TOOL_NAME = regex_test regex_test_OBJC_FILES = main.m regex_HEADERS = include $(GNUSTEP_MAKEFILES)/aggregate.make include $(GNUSTEP_MAKEFILES)/tool.make
main.m
#include <Foundation/Foundation.h>
#include <RegEx/RegEx.h>
int main (int argc, const char **argv)
{
NSRange range;
NSAutoreleasePool *pool;
NSArray *paths;
NSFileManager *fileManager;
NSString *path;
NSBundle *bundle;
Class RegExClass;
int i;
pool = [NSAutoreleasePool new];
搜尋 bundle 的路徑.
fileManager = [NSFileManager defaultManager];
/* Search for the bundles */
paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSLocalDomainMask, YES);
for (i = 0; i < [paths count]; i++)
{
path = [[paths objectAtIndex: i] stringByAppendingPathComponent: @"Bundles/RegEx.bundle"];
if ([fileManager fileExistsAtPath: path])
break;
}
根據路徑取得 bundle, 並從 bundle 中取出類別.
bundle = [NSBundle bundleWithPath: path]; RegExClass = [bundle principalClass];
取得類別後即可直接使用, 如同使用程式庫一般.
range = [RegExClass rangeOfString: @"middle"
inString: @"head middle end"];
NSLog(@"%@", NSStringFromRange(range));
RELEASE(pool);
return 0;
}
程式碼在此: RegEx-2-src.tar.gz
使用 bundle 的好處在放如果不到 bundle, 可以在程式中直接取消相關功能. 換言之, 一個程式可以根據使用者所安裝的 bundle 提供不同的服務. Bundle 也可以當做 plug-in 使用. 例如一個程式可以有很多的 bundle 來處理不同的檔案格式. 隨檔案格式的不同, 找到可以處理其格式的 bundle 來處理該檔案. 這些 bundle 中的類別只要使用相同的介面即可, 這可例用 protocol 或是繼承自相同 superclass 來達成. 如果想要安裝 bundle 的檔頭, 可以比照程式庫的用法, 在 GNUmakefile 中使用 _HEADER_FILES 指定要安裝的檔頭及路徑.
Bundle 是相當有用的功能, 值得花點時間研究一下. Bundle 亦可攜帶如影像或聲音等資源.