java调用cmd编译(二)

利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。
这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。

如何使用Java来编译运行C文件(二)

前言

码农的小日子过得好好的,指导老师一个兴起要求搞一个自己的在线编译网站,我们这种做小弟的只能老老实实地去搞。还好刚刚结束了考试与比赛,因为各种原因导致原定于寒假开工的项目延迟到下学期了,刚好趁这段空闲的时间来搞一搞。其实,自己感觉搞这个的话也挺好玩的~

介绍

利用java后台来编译运行C源文件,主要是为了开发一个在线编译网站的准备。
这里主要介绍如何编译运行C源文件,其实对于其他语言,道理都是一模一样的。

开始动工

批处理文件

先填补上一篇博文剩下的坑。
稍微解释一下情景,我想要cd到E盘,然后调用gcc来编译.c文件,可惜搜索借用网上关于使用&&来连接多条cmd语句不能成功,暂时我只想到利用批处理文件来实现这个功能了。

1
2
3
后缀名为.bat的文件,会被系统自动编译为批处理文件。*.bat 文件中输入,就相当于在dos 窗口中输入一样, 编辑完成后,双击即可运行。
临时变量:在批处理文件 中设置临时变量方式:`set 变量名=变量值`, = 左右不要有空格,使用方式: %变量名% 即可。
批处理文件执行完之后,窗口会自动关闭;若想执行完之后,窗口不自动关闭的话,在文件末尾添加 pause 即可。

调用批处理文件来实现编译

1
2
3
4
Runtime run = Runtime.getRuntime();
String filePath = "bat文件绝对地址";
Process p = run.exec("cmd.exe /c " + filePath);
//这样子就实现了java调用执行多条cmd语句


编译C源文件

.bat文件与.c文件放于同一目录之下,调用Runtime.getRuntime().exec()来执行编译功能。
对于编译来说,只要是获取有可能的错误提示,通过不断接收我们开启的进程的getErrorStream()来获得编译的错误提示。
注意,这里没有涉及到我们构建的进程(会有三条不同的流:标准输入流、标准输出流、错误输入流)多条流之间的交互关系,所以理论上不需要使用waitFor()来获取结果。但在运行过程中就不得不使用这个方法来堵塞进程。
waitFor()容易导致死锁的发生!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    static Process c;
    /**
     * 通过 Runtime 调用批处理文件来编译编源文件
     * 注意:批处理文件需要与编译源文件位于同一目录下
     * @param filePath 批处理文件的绝对路径
     * @throws FileNotFoundException 找不到
     */
    static public void compileApplication(String filePath) throws FileNotFoundException {
        Runtime run = Runtime.getRuntime();
        String cPath = filePath.replace("bat""c");
        File batFile = new File(filePath);
        File cFile = new File(cPath);
        if(!cFile.exists()){
            throw new FileNotFoundException("找不到c编译源文件!");
        }
        if(batFile.exists()) {
            try {
                c = run.exec("cmd.exe /c " + filePath);
                InputStream in = c.getInputStream();
                BufferedInputStream errorIn = new BufferedInputStream(c.getErrorStream());
                int ch;
                StringBuffer errortext = new StringBuffer("");
                //如果有编译错误,读取错误提示
                while ((ch = errorIn.read()) != -1) {
                    errortext.append((char) ch);
                }
                //将编译错误打印出来,并抛出错误异常
                if (!errortext.equals("")) {
                    System.out.println(errortext);
                    //自定义错误异常
                }
                errorIn.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (c != null) {
                    c.destroy();
                }
            }
        } else {
            throw new FileNotFoundException("找不到cmd批处理文件!");
        }
    }

如果编译失败时,我们就能从errortext中拿到编译失败提示:


运行exe文件

上面一小节已经介绍了,如何编译一个C源文件。对一个编译功能来说,我们只要得到他的错误提示就行了,所以只是一直读取进程的错误信息。但运行一个exe文件时,我们不仅要往进程中输入参数、读取进程的标准输出和错误输出。现在,我们就要了解到进程的waitFor()函数。

通过查看JDK帮助文档,我们可以得知:

1. waitFor

public abstract int waitFor()
throws [InterruptedException]

导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程。

返回:

进程的出口值。根据惯例,0 表示正常终止。

抛出:

[InterruptedException] - 如果当前线程在等待时被另一线程中断,则停止等待,抛出 [InterruptedException]。

2. 死锁情况

同时,我通过查阅资料了解到:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的所,甚至死锁。
Runtime对象调用exec()后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。
为了避免这种情况的发生,我在执行exe文件的一个进程启动四条线程,分别对标准输入、标准输出、标准错误流进行读写,还有一个线程进行时间的控制。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    static Process p;
    /**
     * 过 Runtime 调用运行 exe 文件
     * @param filePath exe 文件绝对路径
     * @param inputString 程序读取数据
     * @throws InterruptedException
     * @throws FileNotFoundException
     */
    static public void openApplication(String filePath, final String inputString) throws InterruptedException, FileNotFoundException {
        File file = new File(filePath);
        if(!file.exists()){
            throw new FileNotFoundException("找不到exe文件!");
        }
        try {
            p = Runtime.getRuntime().exec(filePath);
            //exe程序数据输出流,相当于进程标准输入流
            final BufferedInputStream output = new BufferedInputStream(p.getInputStream());
            //exe程序数据输入流
            final BufferedOutputStream input = new BufferedOutputStream(p.getOutputStream());
            //exe程序错误输出流
            final BufferedInputStream errorOutput = new BufferedInputStream(p.getErrorStream());
            final StringBuffer outputText = new StringBuffer("获得信息是: \n");
            final StringBuffer errorText = new StringBuffer("错误信息是:\n");
            /**
             * 向线程进行输入
             */
            new Thread(){
                public void run(){
                    try {
                        System.out.println("执行输入!\n");
                        //将用户输入数据写入
                        input.write(inputString.getBytes());
                        input.flush();//清空存缓
                        System.out.println("----\n读入完毕\n---\n");
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if(input != null) {
                            try {
                                input.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
            /**
             * 获得输出的线程
             */
            new Thread(){
                public void run(){
                    int ch;
                    try {
                        System.out.println("执行输出!\n");
                        //不断获取用户输出
                        while ((ch = output.read()) != -1) {
                            outputText.append((char) ch);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if (output != null){
                            try {
                                output.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
            /**
             * 获得进程的错误提示
             */
            new Thread(){
                public void run(){
                    int ch;
                    try {
                        System.out.println("执行错误输出!\n");
                        //不断获取错误输出
                        while ((ch = errorOutput.read()) != -1) {
                            System.out.println((char) ch);
                            errorText.append((char) ch);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if(errorOutput != null){
                            try {
                                errorOutput.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }.start();
            /**
             * 控制时间的进程
             */
            Thread timeController = new Thread(){
                public void run(){
                    try {
                        System.out.println("执行时间控制!\n");
                        Thread.sleep(5000); //限制运行时间
                        //加入错误提示信息
                        errorText.append("\n运行时间过长!\n");
                        p.destroy();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if(p != null) {
                            p.destroy();
                        }
                    }
                }
            };
            timeController.start();
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
            //记录执行时间
            System.out.println("\n开始执行时间:"+df.format(new Date()));// new Date()为获取当前系统时间
            //一直等待直到“启动成功”
            int   retval   =   p.waitFor();
            //waitfor()结束后,关闭时间控制进程
            timeController.stop();
            //记录结束时间
            System.out.println("\n结束执行时间:"+df.format(new Date()));// new Date()为获取当前系统时间
            System.out.println(outputText);
            System.out.println(errorText);
            System.out.println(retval);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(p!=null) {
                p.destroy();
            }
        }
    }

参考案例

https://github.com/FunriLy/OnlineCompilation

评论