zoukankan      html  css  js  c++  java
  • 本地方法中printf如何传给java--java系统级命名管道

    本地方法中printf如何传给java--java系统级命名管道

    摘自:https://blog.csdn.net/dog250/article/details/6007301 

    2010年11月13日 19:24:00
    阅读数:3929

    遇到很多人,都想知道在调试jni的时候怎么得到c语言printf的输出,这个问题其实有多种解决方法,其中最直观的就是不用printf,直接定义一个本地方法,返回一个jstring,这样在java需要得到信息的时候自己去取就可以了,或者通过c操作java虚拟机的方式,用c代码得到java对象,然后调用其方法把字符串送给java。这两种方式一种是取一种是送,感觉都少不了两者的直接参与,如果能实现一个管道,那就好了!
         java好像不支持命名管道,这样java的管道类就只能在同一个java虚拟机实例中实现线程间通信,并且这种管道不是系统级别的,它只是jvm中的,除非使用套接字。既然java没有内置的命名管道,能否自己定义一个呢?有了它就可以实现跨虚拟机实例的java线程间通信了,并且还可以和c/c++等本地语言编写的进程进行通信。实际上,此问题就是这样被引出的,java通过jni调用了c程序,然而c的printf却无法被java捕获,除非使用文件或者套接字等重定向方案,然而如果系统级管道可用的话,那就再好不过了,这里不需要命名的管道,匿名的就可以,因为实现jni的动态库和java程序是在一个jvm实例中的,因此在一个进程空间内,因此动态库中的printf重定向到该管道即可,要想使用原生的系统级管道,必然需要拿到一个文件的描述符,查遍了java的API。发现有一个FileDescriptor类可用,看了它的java源码,貌似它有一个fd字段,该字段不可设置,是private的,而且它还有一个standardStream私有方法,可以传入一个fd描述符:
    public final class FileDescriptor {
         private int fd;
        private long handle;
        public FileDescriptor() {
        ...
        }
        private FileDescriptor(int fd) {
        this.fd = fd;
            handle = -1;
        }
        static {
            initIDs();
        }
        public static final FileDescriptor in = standardStream(0);
        public static final FileDescriptor out = standardStream(1);
        public static final FileDescriptor err = standardStream(2);
        ...
        private static FileDescriptor standardStream(int fd) {
            FileDescriptor desc = new FileDescriptor();
            desc.handle = set(fd);
            return desc;
        }
    }
    这个FileDescriptor类显得很完备,但是却不好用,其实这是一个低层的类,java根本不希望有人直接使用它,说实话它是在File类之下的,不想linux上,文件是个描述符,windows上文件是个句柄(二者都是内核资源数组的索引),在java中,File是一个对象,而FileDescriptor是对应于操作系统的“文件描述符”,它是和系统相关的,而系统相关的东西,java是不希望用户直接使用的哦!你能从一个File对象中getFD,然而却不能set,也不能通过FileDescriptor构造一个File对象。
         既然该提供的都提供了,那就想办法设置它的fd,将之设置成一个系统级管道的fd。现在问题来了,在哪里创建管道呢?既然java不提供系统级管道的创建方法并且java的FileDescriptor类的fd还是私有的,那么肯定在本地方法中设置了,首先展示出了下面的本地方法,创建了管道并且设置了FileDescriptor的fd字段,本地方法可以完全绕开java虚拟机的限制,它和jvm是并列的,甚至可以操作jvm本身:
    int fdw; //用于后续的本地方法中的输出。
    JNIEXPORT jobject JNICALL Java_test_pipe_1for_1read (JNIEnv * env, jclass cls)
    {
        jobject fdsc;
        jclass cls;
        jmethodID cons_mid;
            jfieldID field;
            cls = (*env)->FindClass(env, "java/io/FileDescriptor");
        pipe(fds);
        fdw = fds[1];
            cons_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
            fdsc = (*env)->NewObject(env, cls, cons_mid);
            field = (*env)->GetFieldID(env, cls, "fd", "I");
            (*env)->SetIntField(env, ret, field, fds[0]);
            return fdsc;
    }
    然后看一下java的调用:
    public class test {
            public native void Wrapper_main();
            public native static FileDescriptor pipe_for_read();
            public FileInputStream in;
            static {
                    System.loadLibrary("stunnel");
            }
            public test() {
                    FileDescriptor  pipe = pipe_for_read();
                    this.in = new FileInputStream(pipe);
            }
            public static void main(String[] args) throws IOException {
                    final test t = new test();
                    new Thread(){
                            public void run(){
                                    t.Wrapper_main();
                            }
                    }.start();
                    while (true) {
                            System.out.println(t.in.read());
                    }
        }
    }
    本地方法Wrapper_main的实现:
    JNIEXPORT void JNICALL Java_test_Wrapper_1main (JNIEnv *env, jobject obj)
    {
        ...
        write(fdw, buf, 1);
        ...
    }
    现在就是fdw如何得到的问题了,可以使用全局变量,但是前提是实现Wrapper_main的库必须和pipe_for_read是同一个,如果不是同一个,那么只能在进程这个层次上查找对应的文件描述符了--它们毕竟属于同一个进程,如果再没有建立其它管道的话,可以通过/proc/pid/fd目录下的描述符查找,或者使用system函数执行lsof命令来找到它...,另外你可以直接使用dup2系统调用将stdout重定向到fdw,但是这样的话你在java中就不能使用System.out了,否则会循环的(因为你已经重定向了标准输出),不管怎样都没有创建一个命名管道更方便,只要有名字就可以了,不在乎在那个动态库中。既然可以在本地方法中创建匿名管道并将fd交给java的FileDscriptor类,那肯定可以创建命名管道,只需要将pipe函数改为mkfifo和open即可,需要的无非是提供一个操作系统级别的文件描述符罢了,并且如果管道名称如果从java中传来的话,还需要将jstring转化为char*。
          事情做到这一步,再进一步就是为java封装一个命名/匿名管道的类了,这样便于以后使用,这难免要写本地方法,但是对于每一个平台写一个本地库就可以了,以后可以直接使用这个封装好的java管道类,一劳永逸!这个管道不是java自带的管道,它可是系统级别的管道哦:
    1.编写NamedPipeStream.java,封装一个NamedPipeStream类,用于支持命名/匿名管道,这里没有使用包:
    import java.io.*;
    public class NamedPipeStream {
            public native static FileDescriptor[] get_named(String name);
            public native static FileDescriptor[] get_anony();
            private FileInputStream in;
            private FileOutputStream out;
            static {
                    System.loadLibrary("pipe");
            }
            public NamedPipeStream(String name) { 
                    FileDescriptor fd[];
                    if (name != null)
                            fd = get_named(name);
                    else
                            fd = get_anony();
                    this.in = new FileInputStream(fd[0]);
                    this.out = new FileOutputStream(fd[1]);
            }
            public NamedPipeStream() {
                    this(null);
            } 
            public int read()throws Exception {
                    return this.in.read();
            }
            public int read(byte[] b)throws Exception  {
                    return this.in.read(b); 
            }
            public int read(byte[] b, int off, int len)throws Exception {
                    return this.in.read(b, off, len);
            }

            public void write(int b)throws Exception {
                    this.out.write(b);
            }
            public void write(byte[] b)throws Exception  {
                    this.out.write(b); 
            }
            public void write(byte[] b, int off, int len)throws Exception {
                    this.out.write(b, off, len);
            }
    }
    2.编写pipe.c的实现文件,用于创建管道并且返回java的FileDescriptor对象:
    #include <jni.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    char* convert(JNIEnv* env, jstring str)
    {
           ...

    JNIEXPORT jobjectArray JNICALL Java_InputNamedPipeStream_get_1named (JNIEnv *env, jclass cls, jstring str)
    {
            jfieldID field_fd;
            jmethodID const_fdesc;
            jclass class_fdesc, class_ioex;
            jobject ret[2];
            int fds[2];
            //这里需要想办法导出两个描述符,否则就需要全局变量了
            if (str) { //创建命名管道
                    char name = convert(env, str); //将jstring转为char*
                    /*
                            1.mkfifo(name, ...);
                            2.open出一个写的为fds[1];
                            3.open出一个读的为fds[0];
                    */
            } else {  //创建匿名管道
                    int rv = pipe(fds);
            }
            class_ioex = (*env)->FindClass(env, "java/io/IOException");
            class_fdesc = (*env)->FindClass(env, "java/io/FileDescriptor");
            const_fdesc = (*env)->GetMethodID(env, class_fdesc, "<init>", "()V");
            ret[0] = (*env)->NewObject(env, class_fdesc, const_fdesc);
            ret[1] = (*env)->NewObject(env, class_fdesc, const_fdesc);
            field_fd = (*env)->GetFieldID(env, class_fdesc, "fd", "I");
            //(*env)->SetIntField(env, ret, field_fd, [根据读或者写将fds的不同元素置于此]);
            (*env)->SetIntField(env, ret[0], field_fd, fds[0]);
            (*env)->SetIntField(env, ret[0], field_fd, fds[1]);
            return ret;
    }
    JNIEXPORT jobjectArray JNICALL Java_InputNamedPipeStream_get_1anony (JNIEnv *env, jclass cls)
    {
            return Java_InputNamedPipeStream_get_1named(env, cls, NULL);
    }
    3.使用NamedPipeStream类(略)。
    PS:java的初衷在于让你避开系统,可以避开系统直接处理业务,可是我却一而再再而三的使用java来接近系统底层,这是一种十分愚蠢的返祖行为!

  • 相关阅读:
    PointToPointNetDevice doesn't support TapBridgeHelper
    NS3系列—10———NS3 NodeContainer
    NS3系列—9———NS3 IP首部校验和
    NS3系列—8———NS3编译运行
    【习题 7-6 UVA
    【Good Bye 2017 C】 New Year and Curling
    【Good Bye 2017 B】 New Year and Buggy Bot
    【Good Bye 2017 A】New Year and Counting Cards
    【Educational Codeforces Round 35 D】Inversion Counting
    【Educational Codeforces Round 35 C】Two Cakes
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/9371047.html
Copyright © 2011-2022 走看看