Write to NSTasks standard input after launch
Asked Answered
H

1

6

I am currently trying to wrap my head around the hole NSTask, NSPipe, NSFileHandle business. So I thought I write a little tool, which can compile and run C code. I also wanted to be able to redirect my stdout and stdin to a text view.

Here is what I got so far. I used code from this post to redirect my stdio: What is the best way to redirect stdout to NSTextView in Cocoa?

NSPipe *inputPipe = [NSPipe pipe];
// redirect stdin to input pipe file handle
dup2([[inputPipe fileHandleForReading] fileDescriptor], STDIN_FILENO);
// curInputHandle is an instance variable of type NSFileHandle
curInputHandle = [inputPipe fileHandleForWriting];

NSPipe *outputPipe = [NSPipe pipe];
NSFileHandle *readHandle = [outputPipe fileHandleForReading];
[readHandle waitForDataInBackgroundAndNotify];
// redirect stdout to output pipe file handle
dup2([[outputPipe fileHandleForWriting] fileDescriptor], STDOUT_FILENO);

// Instead of writing to curInputHandle here I would like to do it later
// when my C program hits a scanf
[curInputHandle writeData:[@"123" dataUsingEncoding:NSUTF8StringEncoding]];

NSTask *runTask = [[[NSTask alloc] init] autorelease];
[runTask setLaunchPath:target]; // target was declared earlier
[runTask setArguments:[NSArray array]];
[runTask launch];

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(stdoutDataAvailable:) name:NSFileHandleReadCompletionNotification object:readHandle];

And here the stdoutDataAvailable method

- (void)stdoutDataAvailable:(NSNotification *)notification
{
    NSFileHandle *handle = (NSFileHandle *)[notification object];
    NSString *str = [[NSString alloc] initWithData:[handle availableData] encoding:NSUTF8StringEncoding];
    [handle waitForDataInBackgroundAndNotify];
    // consoleView is an NSTextView
    [self.consoleView setString:[[self.consoleView string] stringByAppendingFormat:@"Output:\n%@", str]];
}

This Program is working just fine. It is running the C program printing the stdout to my text view and reading "123" from my inputPipe. Like indicated in my comment above I would like to provide the input while the task is running once it is needed.

So there are two questions now.

  1. Is there a way to get a notification as soon as somebody tries to read data from my inputPipe?
  2. If the answer to 1 is no, is there a different approach I can try? Maybe using a class other than NSTask?

Any help, sample code, links to other resources are highly appreciated!

Hypolimnion answered 14/6, 2011 at 22:5 Comment(0)
H
2

I'm not sure whether you can detect a "pull" on an NSPipe. I do have a vague sense that polling for write-availability with select() or using kqueue to look for I/O availability events on the underlying file descriptor of your NSFileHandle might do the trick, but I'm not very familiar with using those facilities in this way.

Do you have to support arbitrary C programs, or is it a special daemon or something you've developed?

If it's your own program, you could watch for requests for feedback on outputPipe, or just blast input onto the inputPipe as you find out what it is you want to send, and let the C program consume it when it's ready; if it's somebody else's code, you may be able to hook scanf and friends using a link-time method (since it's code you're compiling) like the one described in Appendix A-4 of:

http://www.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

The gist of it is to make a .dylib with your custom I/O functions (which may send some sigil to your app indicating that they need input), link that into the built program, set an environment variable (DYLD_BIND_AT_LAUNCH=YES) for the launched task, and run it. Once you've got those hooks in, you can provide whatever conveniences you want for your host program.

Hemo answered 27/6, 2011 at 19:31 Comment(4)
Thanks for your answer Joe. I've recently worked on this problem again and got it working (not exactly what I want though). I created a little GUI for command line programs. When I run the command line program I print stdout to an NSTextView and the user can provide input using an NSTextField. The problem is the the user can write to stdin even if the program does not need any input. That's why it would be perfect if I could get a notification everytime the subprocess is trying to read from stdin. To answer your question: Yes, I would like to support arbitrary C programs.Hypolimnion
I'll take a deeper look into select() and kqueue. Looks interesting. The custom I/O functions might also work but I would prefer not using them.Hypolimnion
I am not entirely sure whether, in the general case, you can know if a program wants input or not. Keep in mind that there are also programs which watch the input pipe for activity using e.g. select or kqueue (hello again!). As a side note, if you want to you can use a single NSTextView for both input and output (see the PseudoTTY project for some slightly old but probably still useful source code). FWIW, I believe that even Terminal.app will happily accept input without caring whether the running program wants it yet, right?Hemo
Thanks again. This is all very helpful. I thinks your right, its fine for the user to provide input even thought the program might not need any.Hypolimnion

© 2022 - 2024 — McMap. All rights reserved.