【问题标题】:Redirecting STDIN and STDOUT of C program in Android在Android中重定向C程序的STDIN和STDOUT
【发布时间】:2014-04-28 22:58:09
【问题描述】:

我正在尝试使用 JNI 将 C 程序移植到 android。我已经能够设置程序并使 java 和 c 一起正常工作。问题是我需要能够使用 STDIN,因为 C 程序从 STDIN 读取输入并通过 STDOUT 返回响应(C 程序是服务器-客户端应用程序)。 我不知道是否值得一提,但 C 程序使用 STDIN_FILENO 文件描述符从 STDIN 读取输入。 如何使用 Java 从 STDOUT 读取和 WRITE 到 STDIN?

我做了一些研究,在以下链接中发现了一些模糊的解释:https://groups.google.com/forum/#!topic/android-ndk/Brm6jPr4C0Y 我不明白。

这是C代码https://github.com/unekwu/kemi/blob/master/jni/dnscat/dnscat.c#L1270

更多详情

C 程序通常从命令行运行,例如 dnscat --dns <ip> <port> 之后它开始监听来自用户的消息。通常从标准输入。

现在在我的 Android 应用中, 我可以通过调用 main 和不同的名称并将 ann 字符串数组传递给它来使用 JNI 运行它。我已经验证程序启动更正。问题是我将如何将消息发送到程序,因为 android 上没有标准输入。

【问题讨论】:

  • 好吧,您可以更改 C 方法的签名并从 java 代码部分传递字节数组。为此,您可以在 java 部分中拆分传入数据(将流转换为字节块并将其传递给 C 部分)。
  • 我认为这在我的情况下不起作用,因为 C 程序不能以这种方式修改(而且我没有编写程序)。我已经包含了我正在使用的 C 程序的链接。重定向标准输入和输出对我来说是最好的解决方案。只是不知道该怎么做。
  • 你的第二个链接给了我一些想法。会尝试并回复您。

标签: java android c android-ndk java-native-interface


【解决方案1】:

我在github创建了一个项目,你可以从here.下载

它创建了 2 个命名管道 (FIFO),一个用于输入,另一个用于输出。

它在本地代码中以只写模式打开管道的一端,在 Java 代码中以只读模式打开管道的另一端。本机代码中的文件描述符映射到 STDOUT 即 1,此后在本机代码中对 STDOUT 的任何写入都将被重定向到可以在 Java 代码中读取的管道的另一端。

它在本地代码中以只读模式打开管道的一端,在 Java 代码中以只写模式打开管道的另一端。本机代码中的文件描述符映射到 STDIN 即 0,此后在 Java 代码中对管道另一端的任何写入都将由本机代码使用 STDIN 读取。

实现STDOUT重定向:

本机代码:

    /*
     * Step 1: Make a named pipe
     * Step 2: Open the pipe in Write only mode. Java code will open it in Read only mode.
     * Step 3: Make STDOUT i.e. 1, a duplicate of opened pipe file descriptor.
     * Step 4: Any writes from now on to STDOUT will be redirected to the the pipe and can be read by Java code.
     */
    int out = mkfifo(outfile, 0664);
    int fdo = open(outfile, O_WRONLY);
    dup2(fdo, 1);
    setbuf(stdout, NULL);
    fprintf(stdout, "This string will be written to %s", outfile);
    fprintf(stdout, "\n");
    fflush(stdout);
    close(fdo);

Java 代码:

/*
 * This thread is used for reading content which will be written by native code using STDOUT.
 */

new Thread(new Runnable() {

    @Override
    public void run() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(mOutfile));
            while(in.ready()) {
                final String str = in.readLine();
                mHandler.post(new Runnable() {

                    @Override
                    public void run() {

                        Toast.makeText(RedirectionJni.this, str, Toast.LENGTH_LONG).show();
                    }

                });
            }
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

实现STDIN重定向:

本机代码:

    /*
     * Step 1: Make a named pipe
     * Step 2: Open the pipe in Read only mode. Java code will open it in Write only mode.
     * Step 3: Make STDIN i.e. 0, a duplicate of opened pipe file descriptor.
     * Step 4: Any reads from STDIN, will be actually read from the pipe and JAVA code will perform write operations.
     */
    int in = mkfifo(infile, 0664);
    int fdi = open(infile, O_RDONLY);
    dup2(fdi, 0);
    char buf[256] = "";
    fscanf(stdin, "%*s %99[^\n]", buf); // Use this format to read white spaces.
    close(fdi);

Java 代码:

/*
 * This thread is used for writing content which will be read by native code using STDIN.
 */
new Thread(new Runnable() {

    @Override
    public void run() {
        BufferedWriter out = null;
        try {
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(mInfile)));
            String content = "This content is written to " + mInfile;
            out.write(content.toCharArray(), 0, content.toCharArray().length);
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start();

如果您需要任何帮助,请告诉我。

【讨论】:

  • STDOUT 有效,但 stdin 无效。当它到达 JNI 代码 int fdi = open(infile, O_RDONLY); 中的这一行时,它会阻塞。不知道为什么。我需要更改 android 的任何权限吗?
  • 打开块,直到打开管道的另一端进行写入。能否在JAVA代码中调试检查是否打开管道进行编写。
  • 这个代码示例在时间方面让我有点困惑。只写一行后它会关闭重定向吗?还是让 stdin/stdout 为接口运行?
  • 请注意引用此代码的任何人:NDK 上的 mkfifo 编译 APP_PLATFORM 21 及更高版本可能会出现问题。看这里:stackoverflow.com/questions/27091001/… 总的来说,我发现实际的设备运行时使用这种技术很麻烦。 “提醒”消息是为了注意标准输入可能会意外关闭或在某些设备上出现其他问题。
  • 另一个提醒。某些设备在来自 Android 框架的标准输出中存在“referenceTable”消息污染:code.google.com/p/android/issues/detail?id=167715 - 您使用的代码可能需要过滤掉这些消息。
【解决方案2】:

在Java代码中,通过

获取命令行
Process p;
p = Runtime.getRuntime().exec("su"); // or execute something else, su is just to get root
DataOutputStream dos = new DataOutputStream(p.getOutputStream());
dos.writeBytes(ForExampleABinaryPath+" &\n");
dos.flush();

// to read (might has to run parallel in different thread)
InputStream is = p.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
is.readLine() // will get you a line o null

使用 write && flush 和 read 并行,您可能能够实现您的目标。 祝你好运

【讨论】:

  • 我的程序使用 JNI 运行。它不是二进制文件,不能以这种方式启动。
  • 如果您只是启动 main 方法,您可以轻松创建一个 android 二进制文件。您只需在其中放置一个适当的 Android.mk 文件后调用 ndk-build 即可。如果您通过 jni 调用更多(不同)方法,则可以通过调用为此目的创建的某些方法将输入发送到 C 代码。
  • ndk-build 创建的可执行文件是非静态的。它会生成一个 .so 文件。不知道如何运行这个
  • 您可能需要稍微调整您的 Android.mk - Android Makefile,以便生成二进制文件。二进制文件通常在文件名后面没有特殊的结尾。使用 adb push 你可以把它放到你的设备上, adb shell, chmod 使它可执行,然后你应该能够从命令行启动它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-14
  • 1970-01-01
  • 2021-04-18
  • 2019-08-25
  • 2012-09-25
  • 1970-01-01
  • 2018-08-30
相关资源
最近更新 更多