ホームページ開発ツール>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。)
 ・ダブルクォーテーションで囲む文字列を含む場合、三重ネストはうまくいかないようで、大外をシングルにする
osascript -e 'do shell script "sudo codesign -f --deep -s \"Developer ID Application: Fuga Hoge\" \"/Users/hoge/Documents/名称未設定.app\"" with administrator privileges'
 ・パスワードはコマンドではなく、Finderのダイアログから入力する。
追記:その後、sudoにシェルコマンドだけで対応する方法も分かりましたので、以下に記しておきます。
pass=piyo && echo $pass | sudo -S codesign --force --options runtime --deep --sign "Developer ID Application: Fuga Hoge" "/Users/hoge/Documents/名称未設定.app"
注)piyoが管理者パスワード

 あと、NSTask(正確にはNSFileHandle)は標準入力(キーボード)からの取り込みにも対応しているのですが、やり方が分かりませんでした。
(実例を探しても、コマンドラインツールしかヒットできなかったし、基本、キーボードはXojoが押さえている訳だから、横取りする何らかの方法が必要な気もするし…)

 これらを踏まえて今回は、(1) mode=1相当、(2) mode=2相当、の2種類を試してみることにしました。
 それぞれの仕様は以下の通りとしました。

 例1(mode=1相当)
 例2(mode=2相当)

 Xojoでの実装(例1)
【ソースコードのコピー&ペーストについて】
ソースコード(グレー背景部分の全文)をコピーし、指定のウィンドウ/クラスにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
ただし、この方法は、メソッドでは問題ないようですが、イベント/アクション/プロパティでは不安定?なので、ペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成
  2. Window1に、PushButton(Name:PushButton1、Default:オン)、TextField(Name:InputField)、TextArea(Name:OutputArea、ReadOnly:オン)を追加
  3. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // シェルコマンドを実行し、結果を出力エリアに表示
      OutputArea.Text = ShellNSTask(InputField.Text)
      
      // 入力フィールドをクリア
      InputField.Text = ""
    End Sub
    
  4. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      // 入力フィールドにフォーカスする
      InputField.SetFocus
    End Sub
    
  5. 以下を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)
  1. Xojoで新規プロジェクトを作成
  2. Window1に、PushButton(Name:PushButton1、Default:オン)、TextField(Name:InputField)、TextArea(Name:OutputArea、ReadOnly:オン)を追加
  3. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // コマンドを送る
      XOTask1.Write(InputField.Text)
      
      // 出力エリアにコマンドの文字列を書き込む(実行結果と区別するため、先頭に$を付加)
      OutputArea.AppendText("$ "+InputField.Text+EndOfLine)
      
      // 入力フィールドをクリア
      InputField.Text = ""
    End Sub
    
  4. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      // 入力フィールドにフォーカスする
      InputField.SetFocus
    End Sub
    
  5. 以下をWindow1にペースト(できなければ、Sub - Endの間をCloseイベントに記述)
    Sub Close() Handles Close
      // タスクの停止
      XOTask1.Close()
    End Sub
    
  6. 新規クラスを作成(名前は、ここでは「XOTask」とした。)
  7. 以下をXOTaskにペースト(できなければ移譲に、名前:ActionDelegateE、引数:notify As Ptr、を追加)
    Private Sub ActionDelegateE(notify As Ptr)
    
  8. 以下をXOTaskにペースト(できなければ移譲に、名前:ActionDelegateO、引数:notify As Ptr、を追加)
    Private Sub ActionDelegateO(notify As Ptr)
    
  9. 以下をXOTaskにペースト(できなければイベント定義に、イベント名:StdErr、引数:notify As Ptr、を追加)
    Event StdErr(msg As String)
    
  10. 以下をXOTaskにペースト(できなければイベント定義に、イベント名:StdOut、引数:notify As Ptr、を追加)
    Event StdOut(msg As String)
    
  11. 以下を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
    
  12. 以下を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
    
  13. 以下を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
    
  14. 以下を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
    
  15. 以下を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
    
  16. 以下を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
    
  17. 以下を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
    
  18. 以下をXOTaskにペースト(できなければプロパティに、名前:myTask、データ型:Ptr、を追加)
    Private Property myTask as Ptr
    
  19. 以下をXOTaskにペースト(できなければプロパティに、名前:NotifyObserver、データ型:Ptr、を追加)
    Private Property NotifyObserver as Ptr
    
  20. 以下をXOTaskにペースト(できなければプロパティに、名前:NotifySelectorE、データ型:Ptr、を追加)
    Private Property NotifySelectorE as Ptr
    
  21. 以下をXOTaskにペースト(できなければプロパティに、名前:NotifySelectorO、データ型:Ptr、を追加)
    Private Property NotifySelectorO as Ptr
    
  22. 以下をXOTaskにペースト
    Private Shared Sub getPostE(id As Ptr, SEL As CString, notify As Ptr)
      ActionHandlerE.Invoke(notify)  // インスタンス側のメソッドを呼び出す
    End Sub
    
  23. 以下をXOTaskにペースト
    Private Shared Sub getPostO(id As Ptr, SEL As CString, notify As Ptr)
      ActionHandlerO.Invoke(notify)  // インスタンス側のメソッドを呼び出す
    End Sub
    
  24. 以下をXOTaskにペースト(できなければ共有プロパティに、名前:ActionHandlerE、データ型:ActionDelegateE、を追加)
    Private Shared Property ActionHandlerE as ActionDelegateE
    
  25. 以下をXOTaskにペースト(できなければ共有プロパティに、名前:ActionHandlerO、データ型:ActionDelegateO、を追加)
    Private Shared Property ActionHandlerO as ActionDelegateO
    
  26. Xojoプロジェクトウィンドウ>ナビゲーターのWindow1をクリックし、XOTaskをレイアウトエディタにドラッグ&ドロップ
    (レイアウトエディタ下部とナビゲーターのWindow1>Controlsに、XOTaskクラスのインスタンス(XOTask1)が生成される)
  27. 以下を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
    
  28. 以下を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]