Java是一個需要運作在JVM上的程式語言,因此效能會比原生(native)程式還要來得差一些。不過對於一些比較需要花費硬體資源的運算(例如影像處理、聲音處理),我們還是可以透過Java提供的JNI(Java Native Interface)來連結並使用原生函式庫提供的功能來完成。Rust的函式庫也可以透過JNI來呼叫,在這篇文章中,會介紹如何把任意現有的Rust函式庫拿進Java程式語言中使用。



ShortCrypt

ShortCrypt是筆者開發的跨程式語言的資料加解密函式庫,其介紹文章的網址如下:

https://magiclen.org/short-crypt/

在本篇文章中,將會嘗試把用Rust程式語言實作的ShortCrypt函式庫加上JNI,使其能夠在Java程式語言中使用。

ShortCrypt JNI

建立專案目錄

要將現有的Rust函式庫加上JNI,不一定需要去修改原本Rust函式庫程式專案的程式碼。我們同樣可以先建立出一個新的Cargoc函式庫專案,名為short-crypt-jni

指令如下:

cargo new --lib short-crypt-jni

引用short-crypt套件(crate)

編輯Cargo.toml,在[dependencies]區塊下加上:

short-crypt = "*"

引用jni套件(crate)

為了能夠讓我們用Rust程式語言撰寫JNI程式,就需要引用jni這個crate。

編輯Cargo.toml,在[dependencies]區塊下加上:

jni = "*"

引用libc套件(crate)

由於我們需要自己管理記憶體中的ShortCrypt結構實體,因此會需要用到libc提供的free函數,來清除某個已經被分配空間的指標。

編輯Cargo.toml,在[dependencies]區塊下加上:

lib = "*"

Java程式實作

是的,您沒看錯標題。在開始把ShortCrypt的Rust函式庫加上JNI前,我們應該要先規劃好Java程式中哪些地方需要使用到由Rust程式語言實作的方法或是函數,這些方法都需要先使用Java的native修飾子來進行宣告,並且使用System類別下的loadLibrary方法來讀入指定名稱的動態函式庫。

我們在Cargo程式專案的根目錄下,新增一個java目錄,在這個目錄中再新增一個Java原始碼檔案,檔名為ShortCrypt.java。內容如下:

import java.io.IOException;

public class ShortCrypt {
 
    // TODO -----Static Initializer-----
 
	static {
		System.loadLibrary("short_crypt_jni");
	}
	
	// TODO -----Static Classes-----
	
	public static class Cipher {
	    public final byte base;
	    public final byte[] body;
	    
	    public Cipher(final byte base, final byte[] body) {
	        this.base = base;
	        this.body = body;
	    }
	}
	
	public static class DecryptException extends IOException {
	    public DecryptException(final String message) {
	        super(message);
	    }
	    
	    public DecryptException(final String message, final Throwable cause) {
	        super(message, cause);
	    }
	}
	
	// TODO -----Static Native Methods-----
 
	private static native long sc_new(final String key);
	
	// TODO -----Object Constants-----
	
	private final long sc;
	
	// TODO -----Constructors-----
	
	public ShortCrypt(final String key) {
	    this.sc = sc_new(key);
	}
	
	// TODO -----Object Native Methods-----
	
	public native Cipher encryptString(final String plaintext);
	
	public native Cipher encryptBinary(final byte[] plaintext);
	
	public native byte[] decryptToBinary(final Cipher cipher) throws DecryptException;
	
	public native String decryptToString(final Cipher cipher) throws DecryptException;
	
	public native String encryptStringToURLComponent(final String data);
	
	public native String encryptBinaryToURLComponent(final byte[] data);
	
	public native byte[] decryptURLComponentToBinary(final String urlComponent);
	
	public native String decryptURLComponentToString(final String urlComponent);
	
	public native String encryptStringToQRCodeAlphanumeric(final String data);
	
	public native String encryptBinaryToQRCodeAlphanumeric(final byte[] data);
	
	public native byte[] decryptQRCodeAlphanumericToBinary(final String qrCodeAlphanumeric);
	
	public native String decryptQRCodeAlphanumericToString(final String qrCodeAlphanumeric);
	
	public native void close();
}

以上程式中,比較需要特別注意的部份有以下幾點:

  • 如果是使用類Unix系統System.loadLibrary("short_crypt_jni")會去連結的函式庫檔名為libshort_crypt_jni.so;如果是使用Windows系統,它會去連結的函式庫檔名為libshort_crypt_jni.dll;如果是使用macOS系統,它會去連結的函式庫檔名為libshort_crypt_jni.dylib
  • 在Rust程式中可以直接存取到Java程式的類別,因此要建立Cipher類別的物件實體並回傳是可行的。
  • 在Rust程式中可以拋出Java的例外。
  • Java的long型別可以當作指標。
  • 由於Rust的ShortCrypt結構實體不是在Java程式中建立出來的,因此它並不會被垃圾回收,我們必須提供額外的close方法來清除掉不使用的ShortCrypt結構實體。

使用javac建立C/C++的標頭檔(*.h)

javac是Java程式語言的編譯工具,它也可以用來把Java程式中所有用到native關鍵字修飾的方法,轉成C/C++的標頭檔(.h檔)。

指令的用法如下:

javac path/to/java-file.java -h path/to/header-output-folder

例如要編譯java/ShortCrypt.java並且將標頭檔產生至java目錄中的話,指令如下:

javac java/ShortCrypt.java -h java

產生出來的標頭檔的路徑為java/ShortCrypt.h,內容如下:

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

#ifndef _Included_ShortCrypt
#define _Included_ShortCrypt
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ShortCrypt
 * Method:    sc_new
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_ShortCrypt_sc_1new
  (JNIEnv *, jclass, jstring);

/*
 * Class:     ShortCrypt
 * Method:    encryptString
 * Signature: (Ljava/lang/String;)LShortCrypt/Cipher;
 */
JNIEXPORT jobject JNICALL Java_ShortCrypt_encryptString
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    encryptBinary
 * Signature: ([B)LShortCrypt/Cipher;
 */
JNIEXPORT jobject JNICALL Java_ShortCrypt_encryptBinary
  (JNIEnv *, jobject, jbyteArray);

/*
 * Class:     ShortCrypt
 * Method:    decryptToBinary
 * Signature: (LShortCrypt/Cipher;)[B
 */
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptToBinary
  (JNIEnv *, jobject, jobject);

/*
 * Class:     ShortCrypt
 * Method:    decryptToString
 * Signature: (LShortCrypt/Cipher;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptToString
  (JNIEnv *, jobject, jobject);

/*
 * Class:     ShortCrypt
 * Method:    encryptStringToURLComponent
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptStringToURLComponent
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    encryptBinaryToURLComponent
 * Signature: ([B)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptBinaryToURLComponent
  (JNIEnv *, jobject, jbyteArray);

/*
 * Class:     ShortCrypt
 * Method:    decryptURLComponentToBinary
 * Signature: (Ljava/lang/String;)[B
 */
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptURLComponentToBinary
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    decryptURLComponentToString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptURLComponentToString
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    encryptStringToQRCodeAlphanumeric
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptStringToQRCodeAlphanumeric
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    encryptBinaryToQRCodeAlphanumeric
 * Signature: ([B)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_encryptBinaryToQRCodeAlphanumeric
  (JNIEnv *, jobject, jbyteArray);

/*
 * Class:     ShortCrypt
 * Method:    decryptQRCodeAlphanumericToBinary
 * Signature: (Ljava/lang/String;)[B
 */
JNIEXPORT jbyteArray JNICALL Java_ShortCrypt_decryptQRCodeAlphanumericToBinary
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    decryptQRCodeAlphanumericToString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ShortCrypt_decryptQRCodeAlphanumericToString
  (JNIEnv *, jobject, jstring);

/*
 * Class:     ShortCrypt
 * Method:    close
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_ShortCrypt_close
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我們等等要參考這個標頭檔的內容來撰寫Rust程式。

Rust程式實作

src/lib.rs檔案中撰寫以下程式:

extern crate libc;

extern crate jni;

extern crate short_crypt;

use std::ptr;

use jni::JNIEnv;
use jni::objects::{JClass, JObject, JString, JValue};
use jni::sys::{jlong, jobject, jbyteArray, jstring};

use short_crypt::{ShortCrypt, Cipher};

#[inline]
fn throw_decrypt_exception<S: AsRef<str>>(env: &JNIEnv, msg: S) {
    let decrypt_exception_class = env.find_class("ShortCrypt$DecryptException").expect("Should get the DecryptException class.");

    env.throw_new(decrypt_exception_class, msg).expect("Should throw an DecryptException.");
}

#[inline]
fn throw_exception<S: AsRef<str>>(env: &JNIEnv, msg: S) {
    let runtime_exception_class = env.find_class("java/lang/RuntimeException").expect("Should get the RuntimeException class.");

    env.throw_new(runtime_exception_class, msg).expect("Should throw an RuntimeException.");
}

#[inline]
fn ensure_ptr_not_null_or_throw_exception<S: AsRef<str>>(env: &JNIEnv, ptr: jlong, msg: S) -> bool {
    if ptr == 0 {
        throw_exception(env, msg);

        false
    } else {
        true
    }
}

#[inline]
fn try_to_get_short_crypt_or_throw_exception<'a>(env: &JNIEnv, object: JObject) -> Option<&'a ShortCrypt> {
    let sc_ptr = env.get_field(object, "sc", "J").expect("Should get a long value.").j().expect("Should get a long value.");

    if ensure_ptr_not_null_or_throw_exception(env, sc_ptr, "This StringCrypt object is closed.") {
        Some(unsafe {
            &*(sc_ptr as *const ShortCrypt)
        })
    } else {
        None
    }
}

#[inline]
fn get_cipher_or_throw_exception(env: &JNIEnv, cipher: JObject) -> Option<Cipher> {
    if cipher.is_null() {
        throw_exception(&env, "The cipher is null.");

        None
    } else {
        let base = env.get_field(cipher.clone(), "base", "B").expect("Should get a byte.").b().expect("Should get a byte.");

        let body = env.get_field(cipher, "body", "[B").expect("Should get a byte array.").l().expect("Should get a byte array.");

        let body = env.convert_byte_array(body.into_inner()).expect("Should get a byte array.");

        Some((base as u8, body))
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_sc_1new(env: JNIEnv, _class: JClass, key: JString) -> jlong {
    let key: String = env.get_string(key).expect("Should get a string.").into();

    let sc = ShortCrypt::new(key);

    Box::into_raw(Box::new(sc)) as jlong
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptString(env: JNIEnv, object: JObject, plaintext: JString) -> jobject {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let plaintext: String = env.get_string(plaintext).expect("Should get a string.").into();

            let sc_cipher = sc.encrypt(&plaintext);

            let cipher_class = env.find_class("ShortCrypt$Cipher").expect("Should get the Cipher class.");

            let body = env.byte_array_from_slice(sc_cipher.1.as_slice()).unwrap();

            let cipher = env.new_object(cipher_class, "(B[B)V", &[JValue::from(sc_cipher.0), JValue::from(JObject::from(body))]).expect("Should create a Cipher instance.");

            cipher.into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinary(env: JNIEnv, object: JObject, plaintext: jbyteArray) -> jobject {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let plaintext = env.convert_byte_array(plaintext).unwrap();

            let sc_cipher = sc.encrypt(&plaintext);

            let cipher_class = env.find_class("ShortCrypt$Cipher").expect("Should get the Cipher class.");

            let body = env.byte_array_from_slice(sc_cipher.1.as_slice()).unwrap();

            let cipher = env.new_object(cipher_class, "(B[B)V", &[JValue::from(sc_cipher.0), JValue::from(JObject::from(body))]).expect("Should create a Cipher instance.");

            cipher.into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptToBinary(env: JNIEnv, object: JObject, cipher: JObject) -> jbyteArray {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            match get_cipher_or_throw_exception(&env, cipher) {
                Some(cipher) => match sc.decrypt(&cipher) {
                    Ok(data) => {
                        env.byte_array_from_slice(data.as_slice()).unwrap()
                    }
                    Err(message) => {
                        throw_decrypt_exception(&env, message);

                        ptr::null_mut()
                    }
                }
                None => ptr::null_mut()
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptToString(env: JNIEnv, object: JObject, cipher: JObject) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            match get_cipher_or_throw_exception(&env, cipher) {
                Some(cipher) => match sc.decrypt(&cipher) {
                    Ok(data) => {
                        env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
                    }
                    Err(message) => {
                        throw_decrypt_exception(&env, message);

                        ptr::null_mut()
                    }
                }
                None => ptr::null_mut()
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptStringToURLComponent(env: JNIEnv, object: JObject, data: JString) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let data: String = env.get_string(data).expect("Should get a string.").into();

            let result = sc.encrypt_to_url_component(&data);

            env.new_string(result).unwrap().into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinaryToURLComponent(env: JNIEnv, object: JObject, data: jbyteArray) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let data = env.convert_byte_array(data).unwrap();

            let result = sc.encrypt_to_url_component(&data);

            env.new_string(result).unwrap().into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptURLComponentToBinary(env: JNIEnv, object: JObject, url_component: JString) -> jbyteArray {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let url_component: String = env.get_string(url_component).expect("Should get a string.").into();

            match sc.decrypt_url_component(&url_component) {
                Ok(data) => {
                    env.byte_array_from_slice(data.as_slice()).unwrap()
                }
                Err(message) => {
                    throw_decrypt_exception(&env, message);

                    ptr::null_mut()
                }
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptURLComponentToString(env: JNIEnv, object: JObject, url_component: JString) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let url_component: String = env.get_string(url_component).expect("Should get a string.").into();

            match sc.decrypt_url_component(&url_component) {
                Ok(data) => {
                    env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
                }
                Err(message) => {
                    throw_decrypt_exception(&env, message);

                    ptr::null_mut()
                }
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptStringToQRCodeAlphanumeric(env: JNIEnv, object: JObject, data: JString) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let data: String = env.get_string(data).expect("Should get a string.").into();

            let result = sc.encrypt_to_qr_code_alphanumeric(&data);

            env.new_string(result).unwrap().into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_encryptBinaryToQRCodeAlphanumeric(env: JNIEnv, object: JObject, data: jbyteArray) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let data = env.convert_byte_array(data).unwrap();

            let result = sc.encrypt_to_qr_code_alphanumeric(&data);

            env.new_string(result).unwrap().into_inner()
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptQRCodeAlphanumericToBinary(env: JNIEnv, object: JObject, qr_code_alphanumeric: JString) -> jbyteArray {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let qr_code_alphanumeric: String = env.get_string(qr_code_alphanumeric).expect("Should get a string.").into();

            match sc.decrypt_qr_code_alphanumeric(&qr_code_alphanumeric) {
                Ok(data) => {
                    env.byte_array_from_slice(data.as_slice()).unwrap()
                }
                Err(message) => {
                    throw_decrypt_exception(&env, message);

                    ptr::null_mut()
                }
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_decryptQRCodeAlphanumericToString(env: JNIEnv, object: JObject, qr_code_alphanumeric: JString) -> jstring {
    match try_to_get_short_crypt_or_throw_exception(&env, object) {
        Some(sc) => {
            let qr_code_alphanumeric: String = env.get_string(qr_code_alphanumeric).expect("Should get a string.").into();

            match sc.decrypt_qr_code_alphanumeric(&qr_code_alphanumeric) {
                Ok(data) => {
                    env.new_string(String::from_utf8_lossy(data.as_slice())).unwrap().into_inner()
                }
                Err(message) => {
                    throw_decrypt_exception(&env, message);

                    ptr::null_mut()
                }
            }
        }
        None => {
            ptr::null_mut()
        }
    }
}

#[no_mangle]
#[allow(non_snake_case)]
pub extern fn Java_ShortCrypt_close(env: JNIEnv, object: JObject) {
    let sc_ptr = env.get_field(object.clone(), "sc", "J").expect("Should get a long value.").j().expect("Should get a long value.");

    if ensure_ptr_not_null_or_throw_exception(&env, sc_ptr, "This StringCrypt object has been closed.") {
        unsafe {
            libc::free(sc_ptr as *mut libc::c_void);
        }

        env.set_field(object, "sc", "J", JValue::from(0i64)).expect("Should set a long value.");
    }
}

以上程式中,比較需要特別注意的部份有以下幾點:

  • 由於Rust會自動在擁有者被消滅時,去drop其所擁有的實體。所以我們在堆疊中產生出來的ShortCrypt結構實體,要先利用Box結構體移到堆積上,接著再用Box結構體的into_raw關聯函數將其轉成指標(long型態),存在Java的物件欄位中。
  • 為了避免記憶體洩漏(memory leak),所以要提供一個close函數,並在這個函數中透過libc提供的free關聯函數來釋放儲存在堆積中的ShortCrypt結構實體。
  • JNI函數用到的#[no_mangle]屬性和pub extern關鍵字,是為了要保留函數名稱,確保Java程式可以呼叫得到。
  • jni這個crate中,有許多sys::jstringobjects::JStringsys::jobjectobjects::JObject等名稱類似但大小寫不同的型別,它們是不同的型別,有用大寫的表示jni這個crate有為它提供專門的結構來處理,只有用小寫的表示它只有做原生JNI型別的對應。不過不管有沒有大寫,它們都可以被用在JNI函數的參數或是回傳值型別上。建議回傳值型別都用只有小寫的那些,免得還要處理生命周期的問題。
  • Rust標準函式庫的ptr模組提供的null_mut函數,可以讓JNI函數回傳null。(只不過JNI函數的回傳值型別要用小寫的那些)
  • 在Rust程式中拋出Java的例外,並不會立刻中斷之後程式的執行,所以要將已進入拋出例外狀態的流程區分開來。

關於Java的簽名(signature)用字串表示的方式,可以參考以下表格:

種類簽名字串
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
類別L類別路徑;
陣列[型別名稱
方法(參數型別簽名)回傳值型別簽名

例如這個方法:

long f (int n, String s, int[] arr);

其簽名的字串為:

(ILjava/lang/String;[I)J

在JNI中,要使用欄位或是方法時幾乎都需要先透過簽名字串來尋找,所以這個格式一定要了解,才有辦法開發JNI程式。

編譯函式庫

由於Rust的函式庫預設的格式為.rlib,並非C/C++使用的.a.so.dll.dylib等。為了要編譯出Java語言能夠載入的函式庫,必須要去修改Cargo的設定檔,在[lib]區塊中,加上crate-type項目,將其的值設定為["cdylib"]。如下:

[lib]
crate-type = ["cdylib"]

如果不設定crate-type,Cargo會根據src目錄中有無lib.rs檔案來決定要不要加入rlib,也就是crate-type = ["rlib"]

crate-type項目加入cdylib,可以使Cargo在編譯程式專案時,將能用在C/C++程式的動態函式庫也跟著編譯出來。

將設定檔修改好後,執行cargo build --release指令,就可以在target/release目錄中找到動態函式庫檔案啦!目標平台如果是Linux的話,就會看到libshort_crypt_jni.so檔案。

連結函式庫

在使用java指令執行Java程式時,如果要載入動態函式庫,就要在java指令的後面緊接-Djava.library.path=/path/to/library-folder參數。

我們可以在Cargo程式專案根目錄的java目錄中再新增一個ShortCryptExample.java檔案來試試我們的ShortCrypt.java能不能正常被使用。ShortCryptExample.java檔案內容如下:

public class ShortCryptExample {
    public static void main(final String[] args) throws Exception {
        final ShortCrypt sc = new ShortCrypt("magickey");
            
        final String urlComponent = sc.encryptStringToURLComponent("articles");
            
        System.out.println(urlComponent);
        System.out.println(sc.decryptURLComponentToString(urlComponent));

        sc.close();
    }
}

接著將終端機的工作目錄移動到java目錄中,執行以下指令來編譯程式:

javac ShortCrypt.java ShortCryptExample.java

然後用以下指令來執行ShortCryptExample.class

java -Djava.library.path=../target/release ShortCryptExample

這邊要注意的是,-D參數的位置一定要在檔案輸入的參數之前,否則會沒有效果。

ShortCryptExample.class的執行結果如下:

2E87Wx52-Tvo
articles

在Android上使用JNI

交叉編譯

我們可以藉由交叉編譯,來編譯出能在Android系統上載入的動態函式庫。有關於交叉編譯Rust程式的方式,可以參考這篇文章:

https://magiclen.org/rust-cross-compile/

連結函式庫

Android上的Java程式不像一般Windows、Linux、macOS等作業系統上的Java程式一樣要使用java指令工具來執行,所以也就不用弄什麼-Djava.library.path參數。我們只要將針對不同CPU架構編譯出來的動態函式庫(.so檔),分類放在Android Studio程式專案內的app/src/main/jniLibs目錄下的子目錄當中就好了。

子目錄都是用Android ABI來命名,列表如下:

目錄名稱支援的指令集對應的Rust編譯目標(cross)
armeabi-v7aarmeabi
Thumb-2
VFPv3-D16
armv7-linux-androideabi
arm64-v8aAArch64aarch64-linux-android
x86x86 (IA-32)
MMX
SSE/2/3
SSSE3
i686-linux-android
x86_64x86-64
MMX
SSE/2/3
SSSE3
SSE4.1, 4.2
POPCNT
x86_64-linux-android