JNI开发环境配置及初步使用,互相调用
分类:计算机网络

C/C++互相调用,调用

参考:

http://www.cnblogs.com/Yogurshine/p/3913073.html

http://blog.chinaunix.net/uid-24118190-id-2985318.html

参考: http: // www.cnblogs.com/Yogurshine/p/3913073.html ...

如何在Java和C++之间进行互相调用呢?

浅析 C++ 调用 Python 模块

背景

最近的一个项目需要用到Android JNI开发,在这里记录了一下如何从零开始完成一个JNI开发。

我们知道,Android系统本身是包括Java层和C层两部分的,相应的Android开发也分SDK开发和NDK开发两种,SDK开发是Java代码,NDK开发则是C/C++代码,他们之间通过JNI接口来交互,Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码。

图片 1

Android架构、SDK、NDK和JNI

应用场景没有什么好说到了,Java调用底层SDK,SDK代码基本都是C或者C++编写,以及后期的物联网技术,很多都是和底层硬件相关系的,底层硬件相关系的编程,自然要用到C或者C++,上层的androidp平台都是用Java编写的,这个之间如何进行互动,就非常重要了。

浅析 C++ 调用 Python 模块

作为一种胶水语言,Python 能够很容易地调用 CC++ 等语言,也能够通过其他语言调用 Python 的模块。

Python 提供了 C++ 库,使得开发者能很方便地从 C++ 程序中调用 Python 模块。

具体的文档参考官方指南:
Embedding Python in Another Application

环境配置

首先在Android Studio中安装相关开发包,从settings中打开Android SDK,然后安装三个包:

图片 2

开发环境

接下来可参考超级简单的Android Studio jni实现进行配置,并编写hello world

需要注意的是,通过java代码生成c代码时(在新建的类上右键执行javah),有可能会遇到找不到类的错误,这时需要跑一遍App,让它生成这个类文件

图片 3

找不到类文件

这里提供两个思路,jni和jna,据说jna这个框架,实在底层的jni技术之上进行的一个封装,所以还是对jni技术有一个比较好的理解,才是至关重要的!!!

调用方法

相关文件及其功能

主要涉及的文件如下

图片 4

JNI的涉及的文件

在java中定义调用jni的java类,用来从java中调用,并生产jni中的h头文件;

图片 5

java类

在app工程中的build.gradle,用来配置生成和使用jni;

图片 6

gradle

jni文件夹中的Android.mk,用来配置引用、lib包名、待编译的c文件等;

图片 7

Android.mk

jni文件夹中的Application.mk,用来配置生成的lib包名;

图片 8

Applicatioin.mk

jni文件夹中的h头文件,根据java类生成的;

图片 9

h头文件

jni文件夹中的c/cpp/h代码文件,是c层代码;

图片 10

c代码

需要注意的是,如果Android.mk中配置的待编译c文件改名了,在编译so库时会遇到报错,提示改名前的文件找不到,这是因为以前生成的so库在干扰编译,把原so库目录(MyTestApplicationappsrcmainobj)清除即可。

REF
这个作者的系列文章很给力,可参考
jni调用参考
jni调用详细参考

1 链接到 Python 调用库

Python 安装目录下已经包含头文件( include 目录)和库文件 ( Windows 下为 python27.lib)。

使用之前需要链接到此库。

Java层调用C层

一般来说,JNI的业务模式大部分是从Java层调用C层的so库,具体可参考超级简单的Android Studio jni实现中,hello world部分的实现。

直接上demo吧!

2 直接调用 Python 语句

#include "python/Python.h"

int main()
{
    Py_Initialize();    ## 初始化

    PyRun_SimpleString("print 'hello'");

    Py_Finalize();      ## 释放资源
}

C层调用Java层

开发中有时会遇到从C层调用Java层的需求,也就是Java层—>C层so库—>Java层的调用链,其中Java层调用C层so库的方式同上,基本原理是,C层的JNIEnv类型代表Java环境。通过这个JNIEnv*指针,就可以对Java端的代码进行操作。

一段C层调用Java层的方法如下:

图片 11

从C层调用Java层

更详细的内容可以参考AndroidJNI通过C++调用JAVA

package jni;

public class RecorderHandler {
     static  {
        try{
          System.out.println("load jni library start... ");
                  //装载对应的类库,因为我的是mac系统所以生成的是.dylib文件,如果是linux/unix生成的是.so文件
          System.load("/Users/xxx/Documents/rt/libRecorderHandler.dylib");
         // System.load("/root/ww/libRecorderHandler.so");
          System.out.println("load jni library completed");
        }catch(Exception e){
            System.out.println("load jni lib occur error:  " + e.getMessage());
        }
    }


    /**
     * 传入录制命令
     * @param type ref  enum type
     * @param conf_id  id
     * @param conf_alias 别名
     * @param force_jvb jvb
     * @return
     */
    public native int doRecorder(int type,String  conf_id,String conf_alias,String force_jvb);

    /**
     * C++回调上传java
     */
   public native void callback();

}

3 加载 Python 模块并调用函数

~/test 目录下含有 test.py :

def test_add(a, b):
    print 'add ', a, ' and ', b
    return a+b

则可以通过以下代码调用 test_add 函数 :

#include "python/Python.h"
#include 
using namespace std;

int main()
{
    Py_Initialize();    // 初始化

    // 将Python工作路径切换到待调用模块所在目录,一定要保证路径名的正确性
    string path = "~/test";
    string chdir_cmd = string("sys.path.append("") + path + "")";
    const char* cstr_cmd = chdir_cmd.c_str();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString(cstr_cmd);

    // 加载模块
    PyObject* moduleName = PyString_FromString("test"); //模块名,不是文件名
    PyObject* pModule = PyImport_Import(moduleName);
    if (!pModule) // 加载模块失败
    {
        cout << "[ERROR] Python get module failed." << endl;
        return 0;
    }
    cout << "[INFO] Python get module succeed." << endl;

    // 加载函数
    PyObject* pv = PyObject_GetAttrString(pModule, "test_add");
    if (!pv || !PyCallable_Check(pv))  // 验证是否加载成功
    {
        cout << "[ERROR] Can't find funftion (test_add)" << endl;
        return 0;
    }
    cout << "[INFO] Get function (test_add) succeed." << endl;

    // 设置参数
    PyObject* args = PyTuple_New(2);   // 2个参数
    PyObject* arg1 = PyInt_FromLong(4);    // 参数一设为4
    PyObject* arg2 = PyInt_FromLong(3);    // 参数二设为3
    PyTuple_SetItem(args, 0, arg1);
    PyTuple_SetItem(args, 1, arg2);

    // 调用函数
    PyObject* pRet = PyObject_CallObject(pv, args);

    // 获取参数
    if (pRet)  // 验证是否调用成功
    {
        long result = PyInt_AsLong(pRet);
        cout << "result:" << result;
    }

    Py_Finalize();      ## 释放资源

    return 0;
}

结束

如上定义了两个native方法,第一个native方法是直接调用C++代码,第二个native是c++调用java ,声明的这个方法直接调用,我再在C++中编写调用Java部分的代码

参数传递

package dubbo.service.impl;
import javax.annotation.Resource;
import jni.RecorderHandler;
import org.springframework.beans.factory.annotation.Autowired;
import recorder.service.RecorderService;
import service.RecorderResultService;
import service.TestService;
import entity.Recorder;
//dubbo暴漏接口注解的两种方式,这是一种方式,是通过注解暴漏服务,另外一种是通过配置文件xml的形式暴漏服务
//@Service
@Resource
public class RecorderServiceImpl implements RecorderService{

    //用@Reference和@Autowired具有同样的效果,只是在配置文件中需要不同的配置
    //@Reference
    @Autowired
    private TestService testService;
    @Autowired
    private RecorderResultService recorderResultService;

    public enum Type{
        RECORDSTART,RECORDSTOP,RECORDPAUSE,RECORDRESUME
    } 



    public void startRecorder(String location) {
        // TODO Auto-generated method stub
        System.out.println("recorder  start now....." + "location is :  " + location);
        //录制完成记录录制结果:
        this.sendRecorderResult();
    }

    public void  sendRecorderResult(){
        try{

            RecorderHandler  recorderHandler = new RecorderHandler();
            System.out.println("uuuu");
            System.out.println("开始调用录制:  " + recorderHandler.doRecorder(Type.RECORDSTART.ordinal(),"aaa","bbb","ccc"));

        //执行回调
        recorderHandler.callback();

        }catch(Exception e){
            System.out.println("occur error : "+e.getMessage());
        }
    }

    public void recorderCallBack(String str,int i){
        //这里打印成功,代表c++调用java部分成功
        System.out.println("execute  calll back, str is " + str +", and i is " + i );
    }
}

1 C++Python 传递参数

Python 的参数实际上是元组,因此传参实际上就是构造一个合适的元组。

常用的有两种方法:

使用 PyTuple_New 创建元组, PyTuple_SetItem 设置元组值

PyObject* args = PyTuple_New(3);
PyObject* arg1 = Py_BuildValue("i", 100); // 整数参数
PyObject* arg2 = Py_BuildValue("f", 3.14); // 浮点数参数
PyObject* arg3 = Py_BuildValue("s", "hello"); // 字符串参数
PyTuple_SetItem(args, 0, arg1);
PyTuple_SetItem(args, 1, arg2);
PyTuple_SetItem(args, 2, arg3);

直接使用Py_BuildValue构造元组

PyObject* args = Py_BuildValue("ifs", 100, 3.14, "hello");
PyObject* args = Py_BuildValue("()"); // 无参函数

i, s, f之类的格式字符串可以参考 格式字符串

如上是调用的接口:

2 转换 Python 返回值

调用 Python 得到的都是PyObject对象,因此需要使用 Python 提供的库里面的一些函数将返回值转换为 C++ , 例如 PyInt_AsLongPyFloat_AsDoublePyString_AsString 等。

还可以使用 PyArg_ParseTuple 函数来将返回值作为元组解析。

PyArg_Parse 也是一个使用很方便的转换函数。

PyArg_ParseTuplePyArg_Parse 都使用 格式字符串

如何生成.so文件呢?
首先生成头文件: .h文件,如何生成??
生成java class文件以后,利用javah jni.RecorderHandler命令进行生成

注意事项

需要将 Python 的工作目录切换到模块所在路径 按照模块名加载而不是文件名 模块加载或者函数加载需要验证是否成功,否则可能会引起堆栈错误导致程序崩溃 需要使用 Py_DECREF(PyObject*) 来解除对象的引用(以便Python垃圾回收)

C++ 调用 Python 模块 浅析 C++ 调用 Python 模块 作为一种胶水语言, Python 能够很容易地调用 C 、 C++ 等语言,也能够通过其他语言调用...

javah jni.RecorderHandler

生成对应的.h文件如下:这个文件就是定义了一些native的方法头,是不允许修改的

* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_RecorderHandler */

#ifndef _Included_jni_RecorderHandler
#define _Included_jni_RecorderHandler
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jni_RecorderHandler
 * Method:    doRecorder
 * Signature: (ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_jni_RecorderHandler_doRecorder
  (JNIEnv *, jobject, jint, jstring, jstring, jstring);

/*
 * Class:     jni_RecorderHandler
 * Method:    callback
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jni_RecorderHandler_callback
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

编写对应的实现代码:里面包含了,对应的Java调用C++部分的代码,和C++调用Java部分的代码,,执行以下输出结果为:

#include "jni_RecorderHandler.h"
#include <string.h>
#include<stdio.h>
#include<stdlib.h>
JNIEXPORT jint JNICALL Java_jni_RecorderHandler_doRecorder
  (JNIEnv *, jobject, jint, jstring, jstring, jstring)
{
        return 9999;
}

 JNIEXPORT void JNICALL Java_jni_RecorderHandler_callback
 (JNIEnv* env, jobject)
 {

    jclass clazz = NULL;
    jobject jobj = NULL;
    jmethodID mid_construct = NULL;
    jmethodID mid_instance = NULL;
    jstring str_arg = NULL;

    // 1、从classpath路径下搜索RecorderServiceImpl这个类,并返回该类的Class对象
    clazz = env->FindClass("dubbo/service/impl/RecorderServiceImpl");
    if (clazz == NULL) {
        printf("can not find 'dubbo.service.impl.RecorderServiceImpl.'class");
        return;
    }

        // 2、获取类的默认构造方法ID
    mid_construct = env->GetMethodID(clazz, "<init>","()V");
    if (mid_construct == NULL) {
        printf("can not  find default constrct method");
        return;
    }

    // 3、查找实例方法的ID
    mid_instance = env->GetMethodID(clazz, "recorderCallBack", "(Ljava/lang/String;I)V");
    if (mid_instance == NULL) {
          printf(" int dubbo.service.impl.RecorderServiceImpl.class can not find method recorderCallBack");
        return;
    }

    // 4、创建该类的实例
    jobj = env->NewObject(clazz,mid_construct);
    if (jobj == NULL) {
        return;
    }

     // 5、调用对象的实例方法
    str_arg = env->NewStringUTF("i am calling recorderCallBack method");
    env->CallVoidMethod(jobj,mid_instance,str_arg,200);

    // 删除局部引用
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(jobj);
    env->DeleteLocalRef(str_arg);
  }

编译命令可以保存为 complime.sh
然后赋予执行权限: chmod +x complime.sh就可以用./complime.sh执行编译了!
编译程序在mac上编译为.dylib文件

g++ -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/include  -I/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/include/darwin -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include jni_RecorderHandler.cpp -o libRecorderHandler.dylib

在linux文件中编译为.so文件:

g++ -shared -I /opt/jdk1.8.0_92/include -I/opt/jdk1.8.0_92/include/linux  -fPIC jni_RecorderHandler.cpp -o libRecorderHandler.so

执行上面从程序,输入结果为:

图片 12

Paste_Image.png

如上证明Java调用C++和C++调用Java都没有问题了。

本文由美高梅网址发布于计算机网络,转载请注明出处:JNI开发环境配置及初步使用,互相调用

上一篇:问题九十七,最小公倍数最大问题 下一篇:试题收录,序列计数
猜你喜欢
热门排行
精彩图文