2
\$\begingroup\$

I'm writing an app to let me interact and debug my android over USB using the android SDK and the ADB command. I create a Process with redirected input and output streams and basically run "ADB shell", and I want to be able to wait for the prompt, enter commands and parse the results.

Here's a sample running ps and getting the results:

CreateProcess("shell");
string data = await ReadToPrompt(":/ $ ");
_Process.StandardInput.WriteLine("ps");
string echo = await ReadLine(); // my command echoed back
string dataAndPrompt = await ReadToPrompt(":/ $ ");

It works (although I'm getting extra CR in the byte stream, CR/LF is coming as CR/CF/LF), but I can't help but wonder if there's a better way than what I have:

Process _Process;
MemoryStream _Remaining; // bytes left after ReadToPrompt
byte[] _Buffer = new byte[0x10000];

public async Task<string> ReadLine()
{
    return await ReadToPrompt("\r\n");
}

public async Task<string> ReadToPrompt(string prompt)
{
    MemoryStream ms = _Remaining == null ? new MemoryStream() : _Remaining;
    int promptSize = prompt.Length;
    int pos = 0;

    while (true)
    {
        // read what we can from the process's standard output
        int count = await _Process.StandardOutput.BaseStream.ReadAsync(_Buffer, 0, _Buffer.Length);
        ms.Write(_Buffer, 0, count); // write new data to memory stream

        // convert all new bytes to check for prompt
        string testString = Encoding.ASCII.GetString(ms.GetBuffer(), pos, (int)(ms.Position - pos));
        int promptPos = testString.IndexOf(prompt);
        if (promptPos > 0)
        {
            // we found it, result is up to and including prompt
            int stripLength = pos + promptPos + promptSize;
            string result = Encoding.ASCII.GetString(ms.GetBuffer(), 0, stripLength);

            // store remaining data in a MemoryStream for the next call
            _Remaining = new MemoryStream();
            _Remaining.Write(ms.GetBuffer(), stripLength, (int)(ms.Position - stripLength));
            return result.Replace("\r\r", "\r");
        }

        // we didn't find it but we know it isn't in what we got, so move first position
        // to check to the position - promptSize
        pos = (int)(ms.Position - promptSize);
        pos = (pos < 0) ? 0 : pos;
    }
}

Is there something I can reliably do to keep from creating new MemoryStreams? For instance, could I do an array copy on MemoryStream.GetBuffer() to itself and use MemoryStream.SetLength()? Is there an entirely better way to do it than using async and await?

\$\endgroup\$
1
  • \$\begingroup\$ I don't want to post this as an answer because it's not necessarily in depth enough but you could potentially create one byte array of fixed size and clear it when you're done with each iteration \$\endgroup\$
    – Dan
    Commented Aug 23, 2014 at 13:02

1 Answer 1

1
\$\begingroup\$
MemoryStream ms = _Remaining == null ? new MemoryStream() : _Remaining;

This is exactly what the null-coalescing operator is for:

MemoryStream ms = _Remaining ?? new MemoryStream();

int count = await _Process.StandardOutput.BaseStream.ReadAsync(_Buffer, 0, _Buffer.Length);

Why are you using BaseStream? Without it, you could work directly with chars instead of bytes, which saves you having to use Encoding to convert those bytes back to string.


Is there something I can reliably do to keep from creating new MemoryStreams?

Yes: don't use MemoryStream at all. Instead, I would use StringBuilder, since it allows you to remove characters from its start, which is exactly what you need. As an added advantage, it works with chars, not bytes.


Is there an entirely better way to do it than using async and await?

Your use of async-await seems reasonable to me. Though I'm not sure it's actually necessary: I think synchronous methods would have worked just as well.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the answer. I won't always be working with strings, for instance there's a screencap command that should be sending bytes for the image. Since I believe the StandardOutput is buffered, if I use that for characters it will eat some of the bytes of my image in it's buffer. \$\endgroup\$ Commented Aug 23, 2014 at 21:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.