ホームページ>開発ツール>Xojo / Real Studio Trial and Error・CocoaのDeclareでShellを利用する
Xojo / Real Studio Trial and Error
目次
CocoaのDeclareでShellを利用する
はじめに
以下は、Xojo Cocoaビルドについての話題です。
Shellは、Xojoネイティブの機能にもありますが、日本語が絡むと問題があるので、代替方法について調べてみました。
なお検証には、Xojo 2016 Release 3を用いています。(Mac mini mid 2010 + macOS 10.13.6 High Sierra)
方針
XojoネイティブのShellは、どうも日本語を理解できないようです。
mode=1(1コマンドの同期実行)では分かりずらいので、mode=2(対話形式)で実行してみると、例えば「ls」と打つと日本語は全て「?」で表示されます。
mode=2のサンプルプログラムは、言語リファレンス>Shell>Interactive Shellsの項、に載っている。「export LANG=ja_JP.UTF-8」とすれば、表示はできるようになりますが、入力(例えば「cd 名称未設定フォルダ」)は依然として受け付けてくれません。
これは、内部で走っているシェル(shやbash)の設定の話ではなく、XojoのShellクラスが対応していないようです。
なので、代替方法がないか調べてみたところ、NSTaskが使えそうなことが分かりました。
mode=1相当の例は、検索すると結構ヒットするのですが、mode=2相当の例は、出力は見つかるものの、入力はあまりヒットしません。(ちなみに、GoogleでsetStandardInputと入力すると「次の検索結果を表示しています: setStandardOutput」となってしまいます。よっぽど需要がない?)
いくつか情報を組み合わせて、何とか体裁を整えることはできました。
なお、mode=2では、出力(stdout)/エラー(stderr)結果は通知センターを経由しますが、通知センターへのアクセスは前回やりましたのでそれをベースにします。
参考サイト(1):こたつつきみかん » NSTaskでコマンドを実行
参考サイト(2):objective c - Using NSPipe for interactive commands - Stack Overflow
mode=2の問題点(の一つ)は、sudoが通らないことです。
実行すると「sudo: no tty present and no askpass program specified」とメッセージが出てしまいます。
今回のような利用法では、ttyもaskpass programも、対応は難しそうです。
調べると、osascriptからdo shell scriptせよ、というのが定番のようだったので、やってみたらうまくいきました。
注意点としては、
・AppleScriptとShell Commandの文法が混在する形になるので、使い分けに注意。(例えばパスはXojoでいうShellPathではなくNativePath。)
・ダブルクォーテーションで囲む文字列を含む場合、三重ネストはうまくいかないようで、大外をシングルにする
・パスワードはコマンドではなく、Finderのダイアログから入力する。osascript -e 'do shell script "sudo codesign -f --deep -s \"Developer ID Application: Fuga Hoge\" \"/Users/hoge/Documents/名称未設定.app\"" with administrator privileges'
追記:その後、sudoにシェルコマンドだけで対応する方法も分かりましたので、以下に記しておきます。あと、NSTask(正確にはNSFileHandle)は標準入力(キーボード)からの取り込みにも対応しているのですが、やり方が分かりませんでした。
注)piyoが管理者パスワードpass=piyo && echo $pass | sudo -S codesign --force --options runtime --deep --sign "Developer ID Application: Fuga Hoge" "/Users/hoge/Documents/名称未設定.app"
(実例を探しても、コマンドラインツールしかヒットできなかったし、基本、キーボードはXojoが押さえている訳だから、横取りする何らかの方法が必要な気もするし…)
これらを踏まえて今回は、(1) mode=1相当、(2) mode=2相当、の2種類を試してみることにしました。
それぞれの仕様は以下の通りとしました。
例1(mode=1相当)
例2(mode=2相当)
- NSTaskは、コマンドごとに生成/破棄する。
- LaunchPathにShell(/bin/bash)を指定し、Argumentsにコマンドを指定。その際、-cオプションを付加する。
注)1コマンドとは入力機会が1回ということで、「cd;ls」のように複数のコマンドを連結することは可能。
- NSTaskは最初に生成し、起動しっ放し。
- 入力は、Xojoのテキストフィールドに打ち込まれたものをwriteData経由で送る(標準入力から直接取り込む方法は使わない。or使えない?)
- 出力/エラー結果は、通知センターから受け取る。そのため、事前に通知センターへの登録を行っておく。
注)終了時の登録解除(removeObserver)は、10.11以降は不要になったとのことなので、ここでは行っていない。- NSTask関連の処理はクラス化する。
- 通知はメソッドではなく、イベントに送る。
Xojoでの実装(例1)
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、日本語を含むデータに対し、シェルコマンドが機能することを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、PushButton(Name:PushButton1、Default:オン)、TextField(Name:InputField)、TextArea(Name:OutputArea、ReadOnly:オン)を追加
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // シェルコマンドを実行し、結果を出力エリアに表示 OutputArea.Text = ShellNSTask(InputField.Text) // 入力フィールドをクリア InputField.Text = "" End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // 入力フィールドにフォーカスする InputField.SetFocus End Sub
- 以下をWindow1にペースト
Private Function ShellNSTask(inText As String) as String // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr // タスクの生成 Dim myTask As Ptr = NSClassFromString("NSTask") myTask = init(alloc(myTask)) // 出力/エラーパイプの生成 Dim myPipeO As Ptr = NSClassFromString("NSPipe") myPipeO = init(alloc(myPipeO)) Dim myPipeE As Ptr = NSClassFromString("NSPipe") myPipeE = init(alloc(myPipeE)) // シェルの指定 Declare Sub setLaunchPath Lib "Cocoa" Selector "setLaunchPath:" (receiver As Ptr, nam As CFStringRef) setLaunchPath(myTask, "/bin/bash") // 引数用アレイの生成 Dim ary As Ptr = NSClassFromString("NSMutableArray") Declare Function myArray Lib "Cocoa" selector "array" (class_id As Ptr) As Ptr ary = myArray(ary) // 引数用アレイに引数をセット Declare Sub addObject Lib "Cocoa" Selector "addObject:" (receiver As Ptr, obj As CFStringRef) addObject(ary, "-c") // -cオプション addObject(ary, inText) // 実行コマンド // 引数のセット Declare Sub setArguments Lib "Cocoa" Selector "setArguments:" (receiver As Ptr, ary As Ptr) setArguments(myTask, ary) // 出力/エラーパイプをタスクにセット Declare Sub setStandardOutput Lib "Cocoa" Selector "setStandardOutput:" (receiver As Ptr, pip As Ptr) setStandardOutput(myTask, myPipeO) Declare Sub setStandardError Lib "Cocoa" Selector "setStandardError:" (receiver As Ptr, pip As Ptr) setStandardError(myTask, myPipeE) // タスクの実行 Declare Sub launch Lib "Cocoa" Selector "launch" (receiver As Ptr) launch(myTask) // タスクが終了するまで待つ Declare Sub waitUntilExit Lib "Cocoa" Selector "waitUntilExit" (receiver As Ptr) waitUntilExit(myTask) // 出力/エラーパイプからファイルハンドルを取得 Declare Function fileHandleForReading Lib "Cocoa" selector "fileHandleForReading" (class_id As Ptr) As Ptr Dim handleO As Ptr = fileHandleForReading(myPipeO) Dim handleE As Ptr = fileHandleForReading(myPipeE) // ファイルハンドルから内容(実行結果。NSData形式)を読み込む Declare Function readDataToEndOfFile Lib "Cocoa" selector "readDataToEndOfFile" (class_id As Ptr) As Ptr Dim dataO As Ptr = readDataToEndOfFile(handleO) Dim dataE As Ptr = readDataToEndOfFile(handleE) // NSDataからNSStringに変換 Dim resultO As Ptr = NSClassFromString("NSString") Dim resultE As Ptr = NSClassFromString("NSString") Declare Function initWithData Lib "Cocoa" selector "initWithData:encoding:" (obj_id As Ptr, dat As Ptr, enc As Integer) As Ptr resultO = initWithData(alloc(resultO), dataO, 4) // 4 = UTF-8 resultE = initWithData(alloc(resultE), dataE, 4) // 4 = UTF-8 // NSStringから文字列を抽出 Declare Function UTF8String Lib "Cocoa" selector "UTF8String" (class_id As Ptr) As CString Dim strO As String = DefineEncoding(UTF8String(resultO), Encodings.UTF8) // UTF-8であることを明示しないと文字化けする Dim strE As String = DefineEncoding(UTF8String(resultE), Encodings.UTF8) // UTF-8であることを明示しないと文字化けする // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(resultO) release(resultE) release(myPipeO) release(myPipeE) release(myTask) // 結果を返す(ここでは、出力とエラーの結果を合体している) return strE+strO End Function
Xojoでの実装(例2)
実行してみたところ、日本語を含むデータに対し、対話型シェルが機能することを確認しました。
- Xojoで新規プロジェクトを作成
- Window1に、PushButton(Name:PushButton1、Default:オン)、TextField(Name:InputField)、TextArea(Name:OutputArea、ReadOnly:オン)を追加
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // コマンドを送る XOTask1.Write(InputField.Text) // 出力エリアにコマンドの文字列を書き込む(実行結果と区別するため、先頭に$を付加) OutputArea.AppendText("$ "+InputField.Text+EndOfLine) // 入力フィールドをクリア InputField.Text = "" End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // 入力フィールドにフォーカスする InputField.SetFocus End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をCloseイベントに記述)
Sub Close() Handles Close // タスクの停止 XOTask1.Close() End Sub
- 新規クラスを作成(名前は、ここでは「XOTask」とした。)
- 以下をXOTaskにペースト(できなければ移譲に、名前:ActionDelegateE、引数:notify As Ptr、を追加)
Private Sub ActionDelegateE(notify As Ptr)
- 以下をXOTaskにペースト(できなければ移譲に、名前:ActionDelegateO、引数:notify As Ptr、を追加)
Private Sub ActionDelegateO(notify As Ptr)
- 以下をXOTaskにペースト(できなければイベント定義に、イベント名:StdErr、引数:notify As Ptr、を追加)
Event StdErr(msg As String)
- 以下をXOTaskにペースト(できなければイベント定義に、イベント名:StdOut、引数:notify As Ptr、を追加)
Event StdOut(msg As String)
- 以下をXOTaskにペースト
Public Sub Close() // タスクの停止 Declare Sub terminate Lib "Cocoa" Selector "terminate" (receiver As Ptr) terminate(myTask) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(myTask) End Sub
- 以下をXOTaskにペースト
Public Sub Constructor() // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr // タスクの生成 myTask = NSClassFromString("NSTask") myTask = init(alloc(myTask)) // パイプの生成 Dim inputpipe As Ptr = NSClassFromString("NSPipe") inputpipe = init(alloc(inputpipe)) Dim outputpipe As Ptr = NSClassFromString("NSPipe") outputpipe = init(alloc(outputpipe)) Dim errorpipe As Ptr = NSClassFromString("NSPipe") errorpipe = init(alloc(errorpipe)) // 起動するタスクにbashを指定 Declare Sub setLaunchPath Lib "Cocoa" Selector "setLaunchPath:" (receiver As Ptr, nam As CFStringRef) setLaunchPath(myTask, "/bin/bash") // タスクにパイプをセット Declare Sub setStandardInput Lib "Cocoa" Selector "setStandardInput:" (receiver As Ptr, pip As Ptr) setStandardInput(myTask, inputpipe) Declare Sub setStandardOutput Lib "Cocoa" Selector "setStandardOutput:" (receiver As Ptr, pip As Ptr) setStandardOutput(myTask, outputpipe) Declare Sub setStandardError Lib "Cocoa" Selector "setStandardError:" (receiver As Ptr, pip As Ptr) setStandardError(myTask, errorpipe) // タスクの起動 Declare Sub launch Lib "Cocoa" Selector "launch" (receiver As Ptr) launch(myTask) // 出力/エラーファイルハンドルの取得 Declare Function fileHandleForReading Lib "Cocoa" selector "fileHandleForReading" (class_id As Ptr) As Ptr Dim output As Ptr = fileHandleForReading(outputpipe) Dim error As Ptr = fileHandleForReading(errorpipe) // 通知センターに登録 SetNotificationCenter(output, error) // ファイルハンドルをバックグラウンドで読み込んで通知 Declare Sub readInBackgroundAndNotify Lib "Cocoa" Selector "readInBackgroundAndNotify" (receiver As Ptr) readInBackgroundAndNotify(output) readInBackgroundAndNotify(error) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(inputpipe) release(outputpipe) release(errorpipe) // インスタンス側で通知を受け取るメソッドを登録 ActionHandlerO = AddressOf PostGetOutput ActionHandlerE = AddressOf PostGetError End Sub
- 以下をXOTaskにペースト
Private Sub MakeObserver() // 文字列を指定してクラスオブジェクト/セレクタを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr Declare Function NSSelectorFromString Lib "Cocoa" (aSelName As CFStringRef) As Ptr // Declare宣言 Declare Function objc_allocateClassPair Lib "Cocoa" (superclass As Ptr, name As CString, extraBytes As Integer) as Ptr Declare Sub objc_registerClassPair Lib "Cocoa" (cls As Ptr) Declare Function class_addMethod Lib "Cocoa" (cls As Ptr, name As Ptr, imp As Ptr, types As CString) As Boolean // 既にインスタンス作成済なら戻る if NotifyObserver <> nil then return end if // クラス名をmyNotif、メタクラス名をNSObjectにして、生成 Dim newClassId As Ptr = objc_allocateClassPair(NSClassFromString("NSObject"), "myNotif", 0) // ランタイムに登録(参照を可能とするため) objc_registerClassPair newClassId // 通知の受け口となるメソッドを追加(getPostO:をXojo側で用意したgetPostOメソッドで受け取る。) NotifySelectorO = NSSelectorFromString("getPostO:") if not class_addMethod (newClassId, NotifySelectorO, AddressOf getPostO, "@@:@") then msgBox "error." return end if // 通知の受け口となるメソッドを追加(getPostE:をXojo側で用意したgetPostEメソッドで受け取る。) NotifySelectorE = NSSelectorFromString("getPostE:") if not class_addMethod (newClassId, NotifySelectorE, AddressOf getPostE, "@@:@") then msgBox "error." return end if // 上記で生成したクラスのインスタンスを作成 Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function init Lib "Cocoa" selector "init" (obj_id As Ptr) As Ptr Dim targetId As Ptr = init(alloc(newClassId)) // インスタンスを保持 NotifyObserver = targetId End Sub
- 以下をXOTaskにペースト
Private Sub PostGetError(notify As Ptr) Dim str As String // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // notifyが有効なら if notify<>nil then // notifyからuserInfoを取得 Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim info As Ptr = userInfo(notify) if info<>nil then // userInfoが有効なら // userInfoから値を取得 Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr Dim data As Ptr = objectForKey(info, "NSFileHandleNotificationDataItem") // return NSData if data<>nil then // 値が有効なら // 値を取得 Dim result As Ptr = NSClassFromString("NSString") Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithData Lib "Cocoa" selector "initWithData:encoding:" (obj_id As Ptr, dat As Ptr, enc As Integer) As Ptr result = initWithData(alloc(result), data, 4) // 出力エリアにセット Declare Function UTF8String Lib "Cocoa" selector "UTF8String" (class_id As Ptr) As CString str = DefineEncoding(UTF8String(result), Encodings.UTF8) // イベントをレイズ RaiseEvent StdErr(str) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(result) end if end if end if // エラーファイルハンドルの取得 Declare Function standardError Lib "Cocoa" selector "standardError" (obj_id As Ptr) As Ptr Dim errorpipe As Ptr = standardError(myTask) Declare Function fileHandleForReading Lib "Cocoa" selector "fileHandleForReading" (obj_id As Ptr) As Ptr Dim error As Ptr = fileHandleForReading(errorpipe) // ファイルハンドルをバックグラウンドで読み込んで通知 Declare Sub readInBackgroundAndNotify Lib "Cocoa" Selector "readInBackgroundAndNotify" (receiver As Ptr) readInBackgroundAndNotify(error) End Sub
- 以下をXOTaskにペースト
Private Sub PostGetOutput(notify As Ptr) Dim str As String // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // notifyが有効なら if notify<>nil then // notifyからuserInfoを取得 Declare Function userInfo Lib "Cocoa" Selector "userInfo" (receiver As Ptr) As Ptr Dim info As Ptr = userInfo(notify) if info<>nil then // userInfoが有効なら // userInfoから値を取得 Declare Function objectForKey Lib "Cocoa" Selector "objectForKey:" (receiver As Ptr, key As CFStringRef) As Ptr Dim data As Ptr = objectForKey(info, "NSFileHandleNotificationDataItem") // return NSData if data<>nil then // 値が有効なら // 値を取得 Dim result As Ptr = NSClassFromString("NSString") Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithData Lib "Cocoa" selector "initWithData:encoding:" (obj_id As Ptr, dat As Ptr, enc As Integer) As Ptr result = initWithData(alloc(result), data, 4) // 出力エリアにセット Declare Function UTF8String Lib "Cocoa" selector "UTF8String" (class_id As Ptr) As CString str = DefineEncoding(UTF8String(result), Encodings.UTF8) // イベントをレイズ RaiseEvent StdOut(str) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(result) end if end if end if // 出力ファイルハンドルの取得 Declare Function standardOutput Lib "Cocoa" selector "standardOutput" (obj_id As Ptr) As Ptr Dim outputpipe As Ptr = standardOutput(myTask) Declare Function fileHandleForReading Lib "Cocoa" selector "fileHandleForReading" (obj_id As Ptr) As Ptr Dim output As Ptr = fileHandleForReading(outputpipe) // ファイルハンドルをバックグラウンドで読み込んで通知 Declare Sub readInBackgroundAndNotify Lib "Cocoa" Selector "readInBackgroundAndNotify" (receiver As Ptr) readInBackgroundAndNotify(output) End Sub
- 以下をXOTaskにペースト
Private Sub SetNotificationCenter(senderObjO As Ptr, senderObjE As Ptr) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // ObserverとSelectorを生成 MakeObserver() // defaultCenterを取得 Dim nCenter As Ptr = NSClassFromString("NSNotificationCenter") Declare Function defaultCenter Lib "Cocoa" Selector "defaultCenter" (receiver As Ptr) As Ptr nCenter = defaultCenter(nCenter) // defaultCenterにObserver/Selector/名前/オブジェクトをセット Declare Sub addObserver Lib "Cocoa" Selector "addObserver:selector:name:object:" (receiver As Ptr, obs As Ptr, sel As Ptr, nam As CFStringRef, obj As Ptr) addObserver(nCenter, NotifyObserver, NotifySelectorO, "NSFileHandleReadCompletionNotification", senderObjO) addObserver(nCenter, NotifyObserver, NotifySelectorE, "NSFileHandleReadCompletionNotification", senderObjE) End Sub
- 以下をXOTaskにペースト
Public Sub Write(txt As String) // 文字列を指定してクラスオブジェクトを取得する。最初に一回宣言しておけばよい。 Declare Function NSClassFromString Lib "Cocoa" (aClassName As CFStringRef) As Ptr // 入力ファイルハンドルの取得 Declare Function standardInput Lib "Cocoa" selector "standardInput" (obj_id As Ptr) As Ptr Dim inputpipe As Ptr = standardInput(myTask) Declare Function fileHandleForWriting Lib "Cocoa" selector "fileHandleForWriting" (obj_id As Ptr) As Ptr Dim input As Ptr = fileHandleForWriting(inputpipe) // 入力文字列をNSData型に変換 Dim str As Ptr = NSClassFromString("NSString") Declare Function alloc Lib "Cocoa" selector "alloc" (class_id As Ptr) As Ptr Declare Function initWithString Lib "Cocoa" selector "initWithString:" (obj_id As Ptr, dat As CFStringRef) As Ptr str = initWithString(alloc(str), txt+EndOfLine) Declare Function dataUsingEncoding Lib "Cocoa" selector "dataUsingEncoding:" (obj_id As Ptr, dat As Integer) As Ptr Dim data As Ptr = dataUsingEncoding(str, 4) // 入力ファイルハンドルにデータを書き込む Declare Sub writeData Lib "Cocoa" selector "writeData:" (obj_id As Ptr, dat As Ptr) writeData(input, data) // clean up Declare Sub release Lib "Cocoa" Selector "release" (receiver As Ptr) release(str) End Sub
- 以下をXOTaskにペースト(できなければプロパティに、名前:myTask、データ型:Ptr、を追加)
Private Property myTask as Ptr
- 以下をXOTaskにペースト(できなければプロパティに、名前:NotifyObserver、データ型:Ptr、を追加)
Private Property NotifyObserver as Ptr
- 以下をXOTaskにペースト(できなければプロパティに、名前:NotifySelectorE、データ型:Ptr、を追加)
Private Property NotifySelectorE as Ptr
- 以下をXOTaskにペースト(できなければプロパティに、名前:NotifySelectorO、データ型:Ptr、を追加)
Private Property NotifySelectorO as Ptr
- 以下をXOTaskにペースト
Private Shared Sub getPostE(id As Ptr, SEL As CString, notify As Ptr) ActionHandlerE.Invoke(notify) // インスタンス側のメソッドを呼び出す End Sub
- 以下をXOTaskにペースト
Private Shared Sub getPostO(id As Ptr, SEL As CString, notify As Ptr) ActionHandlerO.Invoke(notify) // インスタンス側のメソッドを呼び出す End Sub
- 以下をXOTaskにペースト(できなければ共有プロパティに、名前:ActionHandlerE、データ型:ActionDelegateE、を追加)
Private Shared Property ActionHandlerE as ActionDelegateE
- 以下をXOTaskにペースト(できなければ共有プロパティに、名前:ActionHandlerO、データ型:ActionDelegateO、を追加)
Private Shared Property ActionHandlerO as ActionDelegateO
- Xojoプロジェクトウィンドウ>ナビゲーターのWindow1をクリックし、XOTaskをレイアウトエディタにドラッグ&ドロップ
(レイアウトエディタ下部とナビゲーターのWindow1>Controlsに、XOTaskクラスのインスタンス(XOTask1)が生成される)- 以下をXOTask1にペースト(できなければ、Sub - Endの間をStdErrイベントに記述)
Sub StdErr(msg As String) Handles StdErr // 出力エリアの末尾に結果を書き込む OutputArea.AppendText(msg) Dim lastLine as integer = OutputArea.LineNumAtCharPos(OutputArea.Text.Len()) if lastLine > 2 then lastLine = lastLine - 1 end OutputArea.ScrollPosition = lastLine End Sub
- 以下をXOTask1にペースト(できなければ、Sub - Endの間をStdOutイベントに記述)
Sub StdOut(msg As String) Handles StdOut // 出力エリアの末尾に結果を書き込む OutputArea.AppendText(msg) Dim lastLine as integer = OutputArea.LineNumAtCharPos(OutputArea.Text.Len()) if lastLine > 2 then lastLine = lastLine - 1 end OutputArea.ScrollPosition = lastLine End Sub
おわりに
mode=2では、コマンドの終了待ちは行っていないので、実行中に次のコマンドを打ち込んだりした時の動作は未知数です。また、中断もできません。
別タスクでpsとかして、実行状況を把握したりkillしたりすればいいのかもしれませんが、この辺りは(実現可能かも含めて)未検証です。
(終了の判断として、出力/エラーを使うのは、何のメッセージも出さないコマンドもあるので、実用的ではありません。)
ターミナル並みの機能を持たせようと思ったらまだまだ不足(それは無謀?)ですが、自分用として、あらかじめ動作確認済のコマンドを実行する程度になら使えるかも。
お世話になったサイト
貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)
参考サイト(1):こたつつきみかん » NSTaskでコマンドを実行
参考サイト(2):objective c - Using NSPipe for interactive commands - Stack Overflow
更新履歴
2020.11.17 方針に、追記を追加。
2018.10.16 新規作成
[Home] [MacSoft] [Donation] [History] [Privacy Policy] [Affiliate Policy]