除了應用程式 (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 亦可攜帶如影像或聲音等資源.