6

私がやろうとしていること

私の Cocoa アプリでは、一連のコマンドライン プログラムを実行する必要があります。これらのほとんどは非対話型であるため、いくつかのコマンド ライン引数を指定して起動すると、目的の処理を実行し、何かを出力して終了します。プログラムの 1 つは対話型であるため、テキストとプロンプトを stdout に出力し、stdin での入力を期待します。これは、quit コマンドを送信するまで続きます。

機能するもの

大量のデータを stdout にダンプしてから終了するだけの非対話型プログラムは、比較的単純です。

  • NSPipestdout/stdin/stderr の を作成します
  • NSTaskそれらのパイプで起動します

次に、どちらか

  • NSFileHandleパイプのもう一方の端がストリームの最後まですべてのデータを読み取り、タスクの終了時に一度に処理するように取得します

また

  • 出力パイプのもう一方の端-fileDescriptorから を取得します。NSFileHandle
  • ノンブロッキング モードを使用するようにファイル記述子を設定する
  • を使用して、これらのファイル記述子のそれぞれで GCD ディスパッチ ソースを作成します。dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, ...
  • ディスパッチソースを再開し、それがスローするデータを使用して処理しますread()
  • タスクが終了し、パイプ ファイル記述子が EOF を報告するまで続行します ( read()0 バイトの読み取りが報告されます)。

うまくいかないこと

どちらのアプローチも、インタラクティブ ツールでは完全に機能しません。明らかに、プログラムが終了するまで待ちきれません。プログラムはコマンド プロンプトで待機していて、指示しない限り終了しないからです。一方、NSPipeデータをバッファリングするため、CLI プログラムがたまたまパイプを明示的にフラッシュしない限り、バッファサイズのチャンクで受信しますが、私の場合はそうではありません。最初のコマンド プロンプトはバッファー サイズよりもはるかに小さいため、何も受信せず、そのまま待機します。ノーゴーNSPipeもそうです。

いくつかの調査の後、NSPipe の代わりに疑似端末(pty)を使用する必要があると判断しました。残念ながら、私はそれを機能させるのに問題がありました。

私が試したこと

stdout パイプの代わりに、次のように pty を作成します。

struct termios termp;
bzero(&termp, sizeof(termp));
int res = openpty(&masterFD, &slaveFD, NULL, &termp, NULL);

これにより、2 つのファイル記述子が得られます。これは、標準出力のみ、または標準出力と標準入力の両方でにslaveFD渡さNSFileHandleれます。NSTask次に、マスター側から通常の非同期読み取りを実行しようとします。

ターミナル ウィンドウで制御しているプログラムを実行すると、最初に 2 行のテキストが出力されます。1 行目は改行を含めて 18 バイト、もう 1 行は 22 バイトで、コマンド プロンプトに改行はありません。これらの 40 バイトの後、入力を待ちます。

stdout に pty を使用すると、制御対象のプログラムから 18 バイトの出力 (改行で終わる正確に 1 行) を受け取り、それ以上は受け取りません。最初の 18 バイトの後にすべてがそこに留まり、それ以上のイベントはありません。GCD イベント ソースのハンドラーは呼び出されません。

stdin にも pty を使用すると、通常は 19 バイトの出力 (前述の行と次の行の 1 文字) を受け取り、制御されたプログラムはすぐに終了します。データの読み取りを試行する前に少し待った場合 (またはスケジューリング ノイズによって少し一時停止した場合)、プログラムが再び即座に停止する前に、実際には 40 バイト全体を取得します。

追加の行き止まり

ある時点で、非同期読み取りコードに欠陥があるのではないかと考えていたので、NSFileHandles とその-readInBackgroundAndNotifyメソッドを使用してすべてをやり直しました。これは、GCD を使用する場合と同じように動作しました。NSFileHandle(私は当初、 API よりもGCD を選択しましたNSFileHandle

質問

無駄な試みを 1 日以上続けた後、この時点にたどり着いたので、何らかの助けが必要でした。私がやろうとしていることには根本的な問題がありますか? 標準入力を pty に接続するとプログラムが終了するのはなぜですか? 私は pty のマスターエンドを閉じていないので、EOF を受け取るべきではありません。stdin は別として、なぜ 1 行分の出力しか得られないのですか? pty のファイル記述子で I/O を実行する方法に問題はありますか? マスターとスレーブ エンドを正しく使用していますか? マスターは制御プロセスで、スレーブは NSTask で?

私が試していないこと

これまでのところ、パイプと pty でノンブロッキング (非同期) I/O しか実行していません。私が考えることができる唯一のことは、pty が単にそれをサポートしていないということです。(そうであれば、なぜfcntl(fd, F_SETFL, O_NONBLOCK);成功するのでしょうか?) 代わりに、バックグラウンド スレッドでブロッキング I/O を実行して、メイン スレッドにメッセージを送信することができます。マルチスレッドに対処する必要がないようにしたかったのですが、これらすべての API が壊れているように見えることを考えると、非同期 I/O の別の順列を試すよりも時間がかかることはありません。それでも、私が間違っていることを正確に知りたいです。

4

1 に答える 1

2

問題は、内部の stdio ライブラリが出力をバッファリングしている可能性があります。出力は、コマンドライン プログラムが stdio ライブラリまたは fflush()s を介して "\n" を書き込むか、バッファがいっぱいになるか終了する (これにより、 stdio ライブラリを使用して、まだバッファリングされている出力を自動的にフラッシュする)、またはその他の条件が発生する可能性があります。これらの printf 文字列が「\n」で終了している場合、出力が速くなる可能性があります。これは、3 つの出力バッファリング スタイルがあるためです。バッファなし、ライン バッファ (\n ではフラッシュが発生します)、ブロック バッファ (出力バッファがいっぱいになると、自動フラッシュ) です。

出力ファイル記述子が tty (または pty) の場合、stdout のバッファリングはデフォルトで行バッファリングされます。それ以外の場合は、バッファリングされたブロック。stderr はデフォルトでバッファリングされていません。setvbuf() 関数は、バッファリング モードを変更するために使用されます。これらはすべて、ここで説明した標準の BSD UNIX (およびおそらく一般的な UNIX) のものです。

NSTask は ttys/ptys のセットアップを行いません。この場合、printfs は出力されないため、とにかく役に立ちません。\n.

ここでの問題は、コマンドライン プログラム内で setvbuf() を実行する必要があることです。(1) コマンドライン プログラムのソースを持っていて、それを変更してその変更したプログラムを使用できる場合、または (2) コマンドライン プログラムに、出力をバッファリングしないように指示できる機能がある場合を除きます [つまり、 setvbuf() 自体を呼び出す]、これを変更する方法はありません。コマンドライン ユーティリティにこれらの機能が組み込まれている場合を除き (これはまれです)、特定の時点でフラッシュを強制したり、stdio バッファリング動作を変更したりするために、親はこの方法でサブプロセスに影響を与えることはできません。

出典: Re: NSTask、NSPipe および対話型 UNIX コマンド

于 2012-09-25T16:30:30.630 に答える