Java调用C/CPP


一、原理

Java调用C/CPP是通过调用C/CPP生成的动态链接库,因此需要保证Java与C的接口一致才能正常调用.可以利用Javah(高版本中为Javac -h)功能生成C接口进行完善C功能即可。最后将C单独编译生成动态链接库由java调用。以下是步骤

1.写Java接口

public class ArraySum {
    
    static {
        String path = "/home/jin/Desktop/java_cpp/";
        System.load(path+"ArraySum.so");
    }

    public native static int complexComputition(int ptr[]);

    public static void main(String[] args) {
        int data[] = new int[100];
        for(int i=0;i<data.length;i++)
        {
            data[i]=i;
        }
        ArraySum as = new ArraySum();
        
        System.out.println(as.complexComputition(data));
    }
}

终端执行javac 编译生成ArraySum.class二进制文件:

jin@jin-PC:~/Desktop/java_cpp$ javac ArraySum.java 
jin@jin-PC:~/Desktop/java_cpp$ ls
ArraySum.class  ArraySum.java

终端执行javac -h 生成c接口:

jin@jin-PC:~/Desktop/java_cpp$ javac -h . ArraySum.java 
jin@jin-PC:~/Desktop/java_cpp$ ls
ArraySum.class  ArraySum.h  ArraySum.java

2.写C/CPP库文件

生成的ArraySum.h头文件

#include <jni.h>


#ifndef _Included_ArraySum
#define _Included_ArraySum
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_ArraySum_complexComputition
  (JNIEnv *, jclass, jintArray);

#ifdef __cplusplus
}
#endif
#endif

自己将源代码补充完整,对应源代码:

#include "ArraySum.h"
#include <iostream>
JNIEXPORT jint JNICALL Java_ArraySum_complexComputition
(JNIEnv *env, jclass, jintArray ptr, jint np){
    std::cout<<"Array Sum From CPP"<<std::endl;
    jint res=0;
    int *p = env->GetIntArrayElements(ptr, nullptr);
    jsize len = env->GetArrayLength(ptr);
    for(int i=0;i<len;i++)
    {
        res+=p[i];
    }
    return res;
}

因为生成的头文件包含了一个jni.h,因此这里需要将java的路径添加到环境变量里面。

CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/home/jin/jdk-15.0.1/include:/home/jin/jdk-15.0.1/include/linux
export CPLUS_INCLUDE_PATH

编译成库文件:

jin@jin-PC:~/Desktop/java_cpp$ ls
ArraySum.class  ArraySum.cpp  ArraySum.h  ArraySum.java
jin@jin-PC:~/Desktop/java_cpp$ g++ ArraySum.cpp --shared -o ArraySum.so -fPIC
jin@jin-PC:~/Desktop/java_cpp$ ls
ArraySum.class  ArraySum.cpp  ArraySum.h  ArraySum.java  ArraySum.so

3.执行Java二进制文件

jin@jin-PC:~/Desktop/java_cpp$ java ArraySum 
Array Sum From CPP
4950

二、Java调用Python


原理

Python拥有非常多非常好用的开源库。Java可以通过调用Python来对Python的库进行调用。
对Python库调用有两种方法,一种是调用Jython,一种是终端执行Python脚本。

第一种方法Jython在我的理解实际上就是Java语言的Python实现,因此可以直接用Python的语法来写,实际上最终还是运行在JRE中。Java调用Jython之后,实际上还是将Python的语言翻译成了通过Jython翻译成了Java,因此确实做到了Java可以调用Python。虽然Java确实可以做到对Python函数类进行调用,但实际上是调用的Jython,也就是说可以跑Python的代码,但是调的不是真正的Python。目前Jython仅发布了2.7版本,而Python最新版本为3.8,2.7版本支持的库不多,以后也会越来越少。也就是说,调用没有任何三方库依赖的Python是没有问题的,如果Python有三方库依赖,就得看Jython支不支持这个库。因此通过Java对Python进行函数传参形式的调用并不是一个好的策略。

第二种方法是在终端调用Python,改方法只需要在java中单独开辟一个进程去执行Python即可。并没有实现Java调用C那样的传参调用。

Java调用Jython运行对Python函数进行调用

1.想要调用的Python

想要调用以下的python脚本,输出参数,保存出图:

import matplotlib.pyplot as plt
import numpy as np

class PlotSinX(object):
    """docstring for ClassName"""
    def __init__(self, begin,end,space):
        self.begin=begin
        self.end=end
        self.space=space
        x = np.arange(self.begin,self.end,self.space)
        y = np.sin(x)
        plt.plot(x,y)

    def show(self):
        plt.show()
    def savefig(self,path):
        plt.savefig(path)
if __name__=="__main__":
    plotsinx = PlotSinX(0,2*np.pi,0.01)
    plotsinx.savefig("./sinx.jpg")

2.Java脚本

给出Java调用Jython脚本:

import org.python.util.PythonInterpreter;
import java.util.Properties;

public class SaveSinX {
    public static void main(String[] args) {
        String pythonClass = "PlotSinX.py";
        String pythonObjName = "plotSinX";
        String pythonClazzName = "PlotSinX";
        PythonInterpreter pythonInterpreter = new PythonInterpreter();
        
        pythonInterpreter.execfile(pythonClass);
        
        System.out.println( "Hello World!" );
    }
}

运行的时候需要安装Jython与Jython的jar包。将Jython的jar包导入项目,即可在里面调用Jython编译器取解析Jython的语法,里面的函数与类,并在Java中实现调用。具体操作过程中,由于很多的Python库Jython2.7已经不在支持,因此没有实现matplotlib出图。
具体可以参考:
1.

Java终端调用Python

虽然Java调用Python并不能像Java调用C/C++一样函数传参等调用那么方便,但是还是可以通过Java在终端对Python进行调用利用到Python的大量开源库。该调用方法是通过Java开启一个新的进程,新进程执行终端命令来调用,参数的传递可以通过字符串传入终端,结果可以从终端打印中截取输出流返回到Java。这会涉及到变量的类型转换,因为无论是输入还是输出,变量类型都是字符串类型的,转换过程会有数值精度损失。另一种参数传递方法是通过文件交互,这可以将数据以二进制的形式从Java输出,在用Python进行读入,也不失为一种好的办法。

1.想要调用的Python

仍然是上一个python脚本,输出参数,保存出图:

import matplotlib.pyplot as plt
import numpy as np

class PlotSinX(object):
    """docstring for ClassName"""
    def __init__(self, begin,end,space):
        self.begin=begin
        self.end=end
        self.space=space
        x = np.arange(self.begin,self.end,self.space)
        y = np.sin(x)
        plt.plot(x,y)

    def show(self):
        plt.show()
    def savefig(self,path):
        plt.savefig(path)
if __name__=="__main__":
    plotsinx = PlotSinX(0,2*np.pi,0.01)
    plotsinx.savefig("./sinx.jpg")
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class SaveSinX {
    public static void main(String[] args) {
    String[] arguments = new String[] { "python3", "/home/jin/Desktop/java_python/PlotSinX.py"};

    try {
        Process process = Runtime.getRuntime().exec(arguments);
        BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = null;
        while ((line = in.readLine()) != null) {
            System.out.println(line);
        }
        in.close();
        int re = process.waitFor();
        System.out.println(re);
    } catch (Exception e) {
        e.printStackTrace();
    }
    }
}

终端执行,成功调用Python,输出sinx.jpg:

jin@jin-PC:~/Desktop/java_python$ javac SaveSinX.java 
jin@jin-PC:~/Desktop/java_python$ java SaveSinX 
0
jin@jin-PC:~/Desktop/java_python$ ls
PlotSinX.py  SaveSinX.class  SaveSinX.java  sinx.jpg
jin@jin-PC:~/Desktop/java_python$

三、Java调用Matlab库

原理

Java调用Matlab是通过Matlab开发工具将Matlab代码打包成Jar二进制包与.java格式的接口文件。需要Java版本与Matlab版本一致,若版本不一致有可能会调用失败。

1.写Matlab脚本

Matlab脚本如下,就对一个矩阵进行求逆:

function res = inverseMat(x)
%INVERSE 此处显示有关此函数的摘要
%   此处显示详细说明
    res = inv(x);
end

2.打包Jar包

在Matlab命令行中输入

deploytool

打开MatlabCompiler界面,选择Library Complier,进入Library Complier面板。TYPE里面选择Java,并添加函数到EXPORTED FUNCTIONS。下面是将这些函数打包成一个类,对类属性进行一些设置,如类的名字(这里为Inversor)等信息。填好以后,点击package,完成打包。

在Matlab工作目录下,生成一个打包的项目文件与一个文件夹。在里面可以找到打包好的Jar包。

在for_testing文件夹下面可以找到Java文件,我们可以看到里面的接口

public class Inversor extends MWComponentInstance<Inversor>
{
    public void inverseMat(List lhs, List rhs) throws MWException;

    public void inverseMat(Object[] lhs, Object[] rhs) throws MWException;

    public Object[] inverseMat(int nargout, Object... rhs) throws MWException;
}

Matlab的函数被打包到了Inversor Class里面,成为了两个类函数。输入lhs是一个list,输出rhs也是一个Object list。

3.在Java中进行调用

Java代码如下:

import com.mathworks.toolbox.javabuilder.*;
import inverseMat.*;

public class JavaCallMatlab {
    public static void main(String[] args) {
        MWNumericArray x = null;
        Object[] output = null;
        
        
        int n = 5;
        
        int[] dims = { n, n };
        
        
        x = MWNumericArray.newInstance(dims, MWClassID.DOUBLE,
        MWComplexity.REAL);

        
        for (int i = 1; i <= n; i++) {
            for(int j=1;j<=n;j++)
            {
                x.set( (i-1)*n + j, Math.random());
            }
        }
        Inversor inv = null;
        try {
            inv = new Inversor();
            output = inv.inverseMat( 1, x);
            System.out.println("Original Matrix:");
            System.out.println(x);
            System.out.println("Matrix inversed by Matlab:");
            System.out.println(output[0]);
        
        } catch (Exception e) {
            System.out.println("Exception: " + e.toString());
        }        
        
    }
}

Java调用Matlab的Jar包时,所有的变量都要用com.mathworks.toolbox.javabuilder中的变量,来完成计算。最后执行上述程序,输出结果如下:

Original Matrix:
    0.3643    0.2529    0.8693    0.9781    0.4976
    0.2114    0.1854    0.3979    0.2533    0.0947
    0.7052    0.5359    0.7140    0.6467    0.3082
    0.6461    0.0348    0.6758    0.4369    0.9079
    0.5899    0.2316    0.7723    0.0703    0.0392
Matrix inversed by Matlab:
    1.8959  -13.4948    2.4991   -0.6116    3.0433
   -4.1840   15.1738    0.2985    0.7852   -4.0712
   -0.3377    6.1134   -2.0705    0.2405    0.2291
    2.8801   -8.7460    1.2695   -1.1444    1.0918
   -2.3238    8.6814   -0.8597    1.8783   -2.7060