ホームページ>開発ツール>Xojo / Real Studio Trial and Error・レガシーAPIを延命する
Xojo / Real Studio Trial and Error
目次
レガシーAPIを延命する
はじめに
以下は、Xojo Cocoa&Carbonビルドについての話題です。
OS9時代から利用されてきたテクノロジー(以下、レガシーAPI)は、さすがに時代とともに非推奨(deprecated)となってきました。
今後新規に作成するアプリケーションで利用することはないとしても、既に利用中のもので多少なりとも延命したい、というニーズはあるかと思い、少し調べてみました。
今回対象としたレガシーAPIは、以下の二つです。
1. リソースフォーク
2. エイリアスパス
なお検証には、Xojo 2014 Release 2を用いています。(Mac mini + OS X 10.10.1 Yosemite)
現状確認
リソースフォーク
エイリアスパス
- Real Studio 2012 R2から扱えなくなった。
- DeRezで読み出しは可。ただし、デベロッパー環境をインストールしていないと使えない?
- macoslibは、対応が十分ではない。
共通
- RealBasicの頃から、標準では非対応。
・CarbonのDeclareでNewAlias/ResolveAliasを使うことで実現。(例えば「REALbasicとFutureBASICで使おうToolbox最新活用テクニック」に記事あり。)- Xojoで上記Declareを使うと、
・エイリアスパスから絶対パスへの変換はOK
・絶対パスからエイリアスパスへの変換はNG(>エンコーディングの関係?)
- macoslibは、当該項目を見つけられず。
- Real Studio 2011 R3では問題なく利用できる。(10.10迄確認済)
方針
いずれも、Xojoの標準機能では対応できないため、考えられるのは、(1)プラグイン、(2)コマンドラインツール(以下、ツール)、あたりでしょう。
アプリケーションの開発のし易さではプラグインの方に分がありますが、ツールの方が(ツール自体の)作り易さでは上なので、まずは後者で作ってみることにしました。
作成にはXcode 6.1を使用したが、標準ではコマンドラインツールが含まれていないので、別途導入しておく。仕様は、以下の通りとしました。
リソースフォーク
エイリアスパス
- 機能は、読み込みのみ
- リソースタイプはTEXTとPICTのみ
- 出力フォーマットは、TEXTはテキスト形式、PICTはtiff形式
- 入力:リソースフォークを含むファイルへのパス、出力用一時フォルダへのパス、出力:展開したリソース(ファイル)
- 出力用一時フォルダの構成は、
+- temp // 出力用一時フォルダ(注:以下の作例では、フォルダ名は日付時刻から自動生成している) +- TEXT // TEXTリソースフォルダ(リソースタイプと一致) +- 256 // (名前は一例。リソースIDと一致) +- PICT // PICTリソースフォルダ(リソースタイプと一致) +- 128.tiff // (名前は一例。リソースIDと一致)
- 絶対パス>エイリアス変換のみ
- 入力:絶対パス、出力:エイリアス情報の文字列表現
リソースフォークの実装
結果、リソースフォークを読み込めることを確認しました。
- Xcode6.1を起動し、File > New > Project...メニューを選択
- ダイアログのリストから「OS X > Application」と選択し、「Command Line Tool」をクリックしてNextボタンを押す
- プロジェクト名を「rsrcCommand」として保存
- プロジェクトの設定を変更
OS X Deployment Target > OS X 10.7
Objective-C Automatic Reference Counting > No
- rsrcCommand.mを開き、中身を全文削除後に、以下をペースト
注1)作者の慣れの関係でCocoa APIを利用しているが、Carbonで統一することも可能かもしれない。#import <Foundation/Foundation.h> #import <AppKit/AppKit.h> static int ExtractResources(NSString *sourceFile, NSString *kind, NSString *destFolder); int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... NSString *pathF = [NSString stringWithUTF8String:argv[1]]; NSString *pathD = [NSString stringWithUTF8String:argv[2]]; NSString *kind = [NSString stringWithUTF8String:argv[3]]; // Get ResourcesFork and Put File int ret = ExtractResources(pathF, kind, pathD); [pool drain]; return ret; } int ExtractResources(NSString *sourceFile, NSString *kind, NSString *destFolder) { NSError *error; OSStatus err; int refnum; int ret = 0; OSType type = 0; if ([kind isEqualToString:@"PICT"]) type = 0x50494354; if ([kind isEqualToString:@"TEXT"]) type = 0x54455854; // Verify the existance of destFolder BOOL isFolder = true; if (![[NSFileManager defaultManager] fileExistsAtPath:destFolder isDirectory:&isFolder] || !isFolder) return -1; // Open the source resource file FSRef resFile; err = FSPathMakeRef((UInt8 *)[sourceFile fileSystemRepresentation], &resFile, false); if (err != noErr) return -2; HFSUniStr255 forkName; FSGetResourceForkName(&forkName); err = FSOpenResourceFile(&resFile, forkName.length, forkName.unicode, fsRdPerm, &refnum); if (err == eofErr) return -3; else if (err != noErr) return -4; UseResFile(refnum); // Extract all resources of the given type Str255 resName; Handle resHand; short theID; ResType theType; int i; int max = Count1Resources(type); for (i=1; i<=max; i++) { // Get ID resHand = Get1IndResource(type, i); GetResInfo(resHand, &theID, &theType, resName); // Load the resource from ID Handle resource = Get1Resource(type, theID); if (resource == nil) { ret = -5; continue; } HLock(resource); NSData *data = [[NSData alloc] initWithBytesNoCopy:*resource length:GetHandleSize(resource) freeWhenDone:NO]; // Get SubFolder ( Create if not Exists ) NSString *destSubFolder = [destFolder stringByAppendingPathComponent:kind]; if ( ![[NSFileManager defaultManager] fileExistsAtPath:destSubFolder] ) [[NSFileManager defaultManager] createDirectoryAtPath:destSubFolder withIntermediateDirectories:YES attributes:nil error:&error]; // Write the resource in a new file NSString *destFile, *destPath; if ([kind isEqualToString:@"TEXT"]) { destFile = [NSString stringWithFormat:@"%d", theID]; destPath = [destSubFolder stringByAppendingPathComponent:destFile]; if( ![[NSFileManager defaultManager] createFileAtPath:destPath contents:data attributes:nil] ) ret = -6; } if ([kind isEqualToString:@"PICT"]) { destFile = [NSString stringWithFormat:@"%d.tiff", theID]; destPath = [destSubFolder stringByAppendingPathComponent:destFile]; NSImage *img = [[[NSImage alloc] initWithData: data] autorelease]; [[img TIFFRepresentation] writeToFile:destPath atomically:YES]; } // Release the resource [data release]; HUnlock(resource); ReleaseResource(resource); } return ret; }
注2)独自のリソースタイプを使用したい場合は、適宜追加してください。
注3)当初、NSApplicationLoad()を記述していたが、不要だったので削除した。(2014.12.02)
- ビルド
- 出来上がったツールを、(ひとまず)Xojoプロジェクトと同じ場所に置く
- Xojo側の、ツールを起動して結果を受け取るメソッドは、以下の通り
注1)一時フォルダはそのままにしておいても問題ないと思われるが、必要ならリソース取得後に削除してください。(中身の詰まったフォルダの削除の仕方は、Xojoリファレンスのサンプルが参考になります。)メソッド名: GetResourceFork 引数: f As FolderItem, kind As String 戻り値型: FolderItem Dim f2 As FolderItem // rsrcCommandの取得(アプリケーションパッケージ内) f2=App.ExecutableFile.Parent.Parent.Child("Applications").Child("rsrcCommand") if f2=nil or f2.Exists=false then // なければ、プロジェクトファイルの場所から取得 f2=App.ExecutableFile.Parent.Parent.Parent.Parent.Child("rsrcCommand") if f2=nil or f2.Exists=false then return nil end if end if Dim appPath As String = f2.NativePath // テンポラリフォルダ内に一時フォルダを作成 Dim dt As New Date Dim fname As String fname=Format(dt.Year,"0000")+Format(dt.Month,"00")+Format(dt.Day,"00") _ +Format(dt.Hour,"00")+Format(dt.Minute,"00")+Format(dt.Second,"00") // 現在日時の取得(フォルダ名を固有化するために用いる) f2=SpecialFolder.Temporary.Child(fname) // テンポラリフォルダ内にフォルダを取得 f2.CreateAsFolder // フォルダの生成 // rsrcCommand実行 Dim s As Shell s = New Shell s.Execute(""""+appPath+""" """+f.NativePath+""" """+f2.NativePath+""" "+kind) if s.ErrorCode <> 0 Then MsgBox("Error code: " + Str(s.ErrorCode)) return nil end if // Sleep 0.1 seconds Dim t As Integer = ticks do loop until ticks-t > 60*0.1 // 一時フォルダを返す return f2
- Xojo側の、リソースフォークにアクセスするメソッドは以下の通り
TEXT:TextInputStreamで開き、.ReadAllで読み込む
PICT:通常の画像ファイルと同じ方法で開く
エイリアスパスの実装
結果、(保存した)エイリアスパスを、絶対パスに復元できることを確認しました。
- Xcode6.1を起動し、File > New > Project...メニューを選択
- ダイアログのリストから「OS X > Application」と選択し、「Command Line Tool」をクリックしてNextボタンを押す
- プロジェクト名を「aliasCommand」として保存
- プロジェクトの設定を変更
OS X Deployment Target > OS X 10.7
Objective-C Automatic Reference Counting > No
- aliasCommand.mを開き、中身を全文削除後に、以下をペースト
#import <Foundation/Foundation.h> static int GetAliasPath(const char* sourceFile); static void HandleToString(AliasHandle ahd); int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // insert code here... // Get Alias int ret = GetAliasPath(argv[1]); [pool drain]; return ret; } int GetAliasPath(const char *sourcePath) { OSStatus err; FSRef theRef; err = FSPathMakeRef((unsigned char *)sourcePath, &theRef, NULL); if (err != 0) return err; AliasHandle ahd; FSNewAlias(nil, &theRef, &ahd); HandleToString(ahd); DisposeHandle((Handle)ahd); return err; } void HandleToString(AliasHandle ahd) { long size = GetHandleSize((Handle)ahd); int state = HGetState((Handle)ahd); HLock((Handle)ahd); unsigned char *ahdc = (unsigned char *)*ahd; unsigned char *strbin = (unsigned char *)malloc(size); for(int i=0; i<size; i++) { strbin[i] = ahdc[i]; } HSetState((Handle)ahd, state); for(int i=0; i<size; i++) { printf("%02x", strbin[i]); } }
- ビルド
- 出来上がったツールを、(ひとまず)Xojoプロジェクトと同じ場所に置く
- Xojo側の、ツールを起動して結果を受け取るメソッドは、以下の通り
注1)行きがかり上、パスを引数にしているが、FolderItemでもよい。(再取得のステップを省略できる。)メソッド名: GetAliasPath 引数: filePath As String 戻り値型: String // パスが空なら戻る if filePath="" then return "" end if // aliasCommandの取得(アプリケーションパッケージ内) Dim f2 As FolderItem f2=App.ExecutableFile.Parent.Parent.Child("Applications").Child("aliasCommand") if f2=nil or f2.Exists=false then // なければ、プロジェクトファイルの場所から取得 f2=App.ExecutableFile.Parent.Parent.Parent.Parent.Child("aliasCommand") if f2=nil or f2.Exists=false then msgBox "not found." return "" end if end if Dim appPath As String = f2.NativePath // 入力ファイルの取得(NativePathを取得するため) f2=GetFolderItem(filePath) // AliasPath実行 Dim s As Shell s = New Shell s.Execute(""""+appPath+""" """+f2.NativePath)+"""" if s.ErrorCode <> 0 Then MsgBox("Error code: " + Str(s.ErrorCode)) return "" end if Dim st As String st=s.Result s.Close // 返ってきた値はバイナリーを文字で表現したものなので、バイナリーに戻す(ただし、結果は文字列として返す) Dim i, cnt As Integer Dim mb As MemoryBlock cnt=st.LenB/2 mb=new MemoryBlock(cnt) for i=0 to cnt-1 mb.Byte(i)=Val("&h"+MidB(st,i*2+1,2)) next return mb.StringValue(0,cnt)
注2)バイナリーに戻すステップが、試行錯誤時の未整理状態のままだったので、クリンアップした。(2014.12.02)
- Xojo側で、CarbonのDeclareメソッドコールと上記メソッドコールを差し替え
おわりに
開発時はいいとして、ビルド時はその都度手作業で、パッケージ内のApplicationsフォルダにコマンドラインツールをコピーする必要があり、面倒ではあります。
やはりプラグイン化した方がいいかも。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):XonTC[05] リソースを扱う
参考サイト(2):FolderItem.Delete - Xojo Documentation
参考サイト(3):cocoa - How i can modernize deprecated FSPathMakeRef, FSIsAliasFile, FSResolveAliasFileWithMountFlags, FSRefMakePath - Stack Overflow
参考サイト(4):Alias Storage | Cocoabuilder
更新履歴
2014.12.02 ソースコードを一部改変
2014.11.28 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]