CGo and JNI

JNI

众所周知,Java 的 native 方法不是用 Java 实现的,比如 hashCode

JNI(Java Native Interface) 直译 Java 本地接口

Java 是运行在 JVM 上的,而 JVM 又将操作系统的绝大数控制权给封闭起来不让开发者用(

所以开发者想用的话就得使用 JNI,用其他系统级编程语言实现这个接口

CGo

CGo 是 Golang 和 C/C++ 进行交互所推出的特性 CGo 作用

CGo + JNI = ?

使用 javah 命令会生成一个 .h 文件,里面定义了 native 方法的接口

开发者只需要实现 .h,然后把它做成 动态链接库 给 java 程序动态加载

使用 CGo 来实现 .h,用 go build -buildmode=c-shared 编译成 动态链接库

计划通!

Code

package jnitest;

public class NativeHello {

    public native void SayHello(); // 定义一个 native 方法

    static {
        // 这里可以把 hi.so 的路径放进环境变量里,然后 loadLibrary()
        System.load("/Users/igxnon/工作目录/GolandProject/test_proj/hi.so");
    }

    public static void main(String[] args) {
        new NativeHello().SayHello();
    }

}

javah 后就会生成一个这么个东西

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

#ifndef _Included_jnitest_NativeHello
#define _Included_jnitest_NativeHello
#ifdef __cplusplus  // 这里兼容 cpp
extern "C" {
#endif
/*
 * Class:     jnitest_NativeHello
 * Method:    SayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_jnitest_NativeHello_SayHello // 需要的就是这个 `Java_jnitest_NativeHello_SayHello(JNIEnv *, jobject)`
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

package main

/*
// cgo 配置,定义了头文件的检索目录,实际上就是定义 jni.h 在哪
#cgo CFLAGS: -I./include

// 导入 javah 生成的头文件
#include "jnitest_NativeHello.h"
 */
import "C" // 启用 CGo

import "fmt"

// 得有个 main 函数,不然 go 不让编译
func main() {

}

// 下面会生成一个 C Java_jnitest_NativeHello_SayHello 函数
//export Java_jnitest_NativeHello_SayHello
func Java_jnitest_NativeHello_SayHello(env *C.JNIEnv, obj C.jobject)  {
	fmt.Println("hello!")
}

go build 后 生成两个文件

java load 动态链接库时不会加载这个,所以写 go 时,不要直接用这里的一些别名 typedef,需要的话在 .go 文件里自行定义

hi.h 如下:

/* Code generated by cmd/cgo; DO NOT EDIT. */

/* package test_proj */


#line 1 "cgo-builtin-export-prolog"

#include <stddef.h> /* for ptrdiff_t below */

#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif

#endif

/* Start of preamble from import "C" comments.  */


#line 3 "hello.go"



#include "jnitest_NativeHello.h"

#line 1 "cgo-generated-wrapper"


/* End of preamble from import "C" comments.  */


/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];

#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif

extern void Java_jnitest_NativeHello_SayHello(JNIEnv* env, jobject obj);

#ifdef __cplusplus
}
#endif

最后把生成的 hi.so 给 java 加载就行了

(放 static 语法块里能保证类装载时就加载,防止用的时候找不到)

static {
    System.load("/Users/igxnon/工作目录/GolandProject/test_proj/hi.so");
}

Result

Furthermore

好玩

native 如它名称一样,本地|原生,使用了 native 的程序大多数移植性不强

为了适配不同的操作系统,可能需要实现多种 native 方法

CGo 这个特性很多语法都奇奇怪怪的,比如必须在 import "C" 上面写注释才会被认做 C 源码,中间有空行都不行,还有名字叫 "C"但里面啥也不是的假包

想念 Java 中的注解....