適用於 Android API 的程式庫包裝函式 屬於 Android Game Development Kit

程式庫包裝函式屬於指令列工具 (CLI),可針對以 Java 編寫的 Android API 產生 C 語言包裝函式程式碼。您可以在原生 Android 應用程式中使用這個程式碼呼叫 Java API,無須手動建立 Java 原生介面 (或稱 JNI)。這項工具可簡化主要以 C 或 C++ 編寫的 Android 應用程式開發作業。

這項工具會單獨或同時針對以下項目產生 C 程式碼:所提供 Java Archive (JAR) 檔案中的公開符號,或在工具設定檔中定義的類別。工具所產生的程式碼不會取代 Java API,而會擔任 C 程式碼和 Java 之間的橋接。應用程式仍需採用包裝的 Java 程式庫,才能納入專案。

下載

下載程式庫包裝函式封存檔案,並將檔案內容解壓縮至所選的目錄。

語法

程式庫包裝函式工具擁有下列指令列語法:

java -jar lw.jar \
  [-i jar-file-to-be-wrapped] \
  [-o output-path] \
  [-c config-file] \
  [-fa allow-list-file] \
  [-fb block-list-file] \
  [--skip_deprecated_symbols]
參數 說明
-i jar-file-to-be-wrapped 產生 C 包裝函式程式碼的 JAR 檔案。系統可能會指定多個 JAR,例如:
-i first_library.jar -i second_library.jar...
-o output-path 所產生程式碼的檔案系統位置。
-c config-file 程式庫包裝函式設定檔的檔案系統路徑。詳情請參閱設定相關章節
-fa allow-list-file 篩選器檔案路徑,該檔案會指定工具要包裝的符號。詳情請參閱篩選器相關章節。
-fb block-list-file 篩選器檔案路徑,該檔案含有不需進行包裝的符號。詳情請參閱篩選器相關章節。
--skip_deprecated_symbols 指示包裝函式工具要略過 @Deprecated 符號。

包裝函式設定檔

程式庫包裝函式設定是 JSON 檔案,可用於控制程式碼產生程序,並採用以下檔案結構。

{
  // An array of type-specific configs. A type config is useful when a user wants to map
  // a Java type to a manually defined C type without generating the code. For example, when a developer
  // has their own implementation of the "java.lang.String" class, they can tell the generator to use it
  // instead of generating it.
  "type_configs": [
    {
      // [Required] Name of a fully qualified Java type.
      "java_type": "java.lang.String",
      // The C type that the java_type will be mapped to.
      "map_to": "MyOwnStringImplementation",
      // A header file that contains the declaration of the "map_to" type.
      "source_of_definition": "my_wrappers/my_own_string_implementation.h",
      // Controls if a value should be passed by pointer or value.
      "pass_by_value": false
    }
  ],
  // An array of package-specific configs.
  "package_configs": [
    {
      // [Required] A name of a Java package that this section regards. A wildchar * can be used at the
      // end of the package name to apply this config to all packages whose name starts with this value.
      "package_name": "androidx.core.app*",
      // A subdirectory relative to the root directory where the generated code will be located.
      "sub_directory": "androidx_generated/",
      // If true, the generated file structure reflects the package name. For example, files generated
      // for the package com.google.tools will be placed in the directory com/google/tools/.
      "file_location_by_package_name": true,
      // A prefix added to all class names from this package.
      "code_prefix": "Gen",
      // A prefix added to all generated file names from this package.
      "file_prefix": = "gen_"
    }
  ],
  // An array of manually defined classes for wrapping. Defining classes manually is useful when a
  // jar file with desired classes are not available or a user needs to wrap just a small part of an SDK.
  "custom_classes": [
    {
      // [Required] A fully-qualified Java class name. To define inner class, use symbol "$", for example
      // "class com.example.OuterClass$InnerClass".
      "class_name": "class java.util.ArrayList<T>",
      // List of methods.
      "methods": [
        "ArrayList()", // Example of a constructor.
        "boolean add(T e)", // Example of a method that takes a generic parameter.
        "T get(int index)", // Example of a method that returns a generic parameter.
        "int size()" // Example of parameterless method.
      ]
    },
  ]
}

篩選器檔案

建議您在預計要包裝的 JAR 檔案中排除一些符號,方法就是在設定中指定篩選器檔案。篩選器檔案是簡單的文字檔案,每一行各定義一個要包裝的符號。篩選器檔案使用下列語法:

java-symbol-name java-jni-type-signature

以下是篩選器檔案範例:

# Class filter
java.util.ArrayList Ljava.util.ArrayList;

# Method filter
java.util.ArrayList.lastIndexOf (Ljava.lang.Object;)I

# Field filter
android.view.KeyEvent.KEYCODE_ENTER I

您可以在設定中提供篩選器檔案,並在檔案中使用 -fa 參數指定要允許包裝的符號,或使用 -fb 參數封鎖不需包裝的符號。這兩個參數可以同時使用。在同時提供這兩種篩選器的情況下,如果符號是在允許的篩選器檔案中定義,且未出現在封鎖的篩選器檔案,系統就會包裝該符號。

情境示例

假設您需要包裝含有以下類別的 JAR 檔案 ChatLibrary.jar

public class ChatManager {
  public static void sendMessage(int userId, String message) {...}
}

C 專案會要求您為這個 JAR 產生原生包裝函式,讓原生 Android 應用程式可以在執行階段呼叫這個包裝函式。請搭配使用下列指令和程式庫包裝函式,產生此程式碼:

java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/

上述指令會產生 C 原始碼至目錄 ./generated_code。所產生的檔案 chat_manager.h 會包含類似下方的程式碼,讓您呼叫專案中的程式庫:

#include "java/lang/string.h"

typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);

如需詳細的情境示例,請參閱「程式庫包裝函式指南」。

工具詳細資料

以下各節將詳細說明程式庫包裝函式的功能。

輸出目錄結構

所有 C 來源檔案和標頭檔案都位於子目錄中,且子目錄會反映所包裝 Java 類別的套件名稱,例如所指定 JAR java.lang.Integer 的包裝函式程式碼會產生至 ./java/lang/integer.[h/cc] 目錄。

您可以使用工具的設定檔案控管這項輸出行為。

物件生命週期

在 C 程式碼中,Java 物件是以稱為包裝函式的不透明指標表示。包裝函式會為相對應 Java 物件管理 JNI 參照。包裝函式可以在下列情境中建立:

  • 透過呼叫函式 MyClass_wrapJniReference(jobject jobj) 包裝現有的 JNI 參照。這個函式不會取得所提供參照的擁有權,而會建立其全域 JNI 參照。
  • 透過建立新物件,這相當於在 Java 中呼叫建構函式 MyClass_construct()
  • 透過傳回函式的新包裝函式,例如 Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

您需要刪除所有不再使用的包裝函式,方法是呼叫專屬的 destroy() 函式 MyClass_destroy(MyClass* instance)

傳回包裝函式的函式會針對每個呼叫為包裝函式分配新的記憶體,即使包裝函式代表同一個 Java 例項也一樣。

舉例來說,如果 Java 方法 Singleton.getInstance() 一律傳回同一個執行個體,C 端的對等函式就會為同一個 Java 例項建立新的包裝函式例項:

Singleton* singleton_a = Singleton_getInsance();
Singleton* singleton_b = Singleton_getInsance();

// singleton_a and singleton_b are different pointers, even though they represent the same Java instance.

處理未參照的類別

無法在提供的 JAR 中找到類別時,程式庫包裝函式會建立包含不透明指標和下列方法的基本實作項目:

  • wrapJniReference()
  • getJniReference()
  • destroy()

程式碼產生作業詳情

執行期間,程式庫包裝函式會在提供工具的 JAR 檔案中,根據檔案中的公開符號產生 C 程式碼。產生的 C 程式碼可能會與包裝的 Java 程式碼有所不同。舉例來說,C 不支援 OOP、一般類型、方法超載或其他 Java 功能。

當產生的 C 程式碼出現這類情況時,其程式碼類型可能會與 C 開發人員的預期不同。下列各節的範例將說明工具透過 Java 程式碼產生 C 的方式。注意:下列範例包含 C/C++ 和 Java 程式碼片段。這些程式碼片段旨在示範工具如何針對各種情況產生程式碼。

類別

在 C 中,類別會以不透明指標表示:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

不透明指標的例項稱為「包裝函式」。包裝函式工具會為每個類別產生額外的支援函式。針對上述範例類別 MyClass,系統會產生下列函式:

// Wraps a JNI reference with MyClass. The 'jobj' must represent MyClass on the Java side.
MyClass* MyClass_wrapJniReference(jobject jobj);

// Return JNI reference associated with the 'MyClass' pointer.
jobject MyClass_getJniReference(const MyClass* object);

// Destroys the object and releases underlying JNI reference.
void MyClass_destroy(const MyClass* object);

建構函式

具有公開或預設建構函式的類別會以特殊函式表示:

C/C++

MyClass* MyClass_construct(String* data);

Java

public class MyClass {
  public MyClass(String data) { ... }
}

方法

方法會以一般函式表示,且函式名稱包含原始類別名稱。代表非靜態例項方法的函式會以表示 Java 物件的結構指標做為第一個參數,並代表該物件呼叫函式。這個方法類似於 this 指標。

C/C++

Result* MyClass_doAction(const MyClass* my_class_instance, int32_t action_id, String* data);
int32_t MyClass_doAction(int32_t a, int32_t b);

Java

public class MyClass {
  public Result doAction(int actionId, String data) { ... }
  public static int doCalculations(int a, int b) { ... }
}

內部類別

內部類別的代表方式非常接近一般類別,但相對應的 C 結構名稱會包含外部類別的鏈結名稱:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

public class MyClass {
  public class InnerClass {...}
}

內部類別方法

內部類別方法是以下列方式表示:

C/C++

bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);

Java

public class MyClass {
  public class InnerClass {
    public boolean setValue(int value) { ... }
  }
}

一般類型

程式庫包裝函式不會直接包裝一般類型,只會針對一般類型例項化作業產生包裝函式。

舉例來說,如果 API 中有 MyGeneric<T> 類別,且該類別有兩個例項化作業,例如 MyGeneric<Integer>MyGeneric<String>,系統就會為這兩個例項化作業產生包裝函式。也就是說,您無法使用不同類型設定建立新的 MyGeneric<T> 類型例項化作業。請參閱以下範例:

C/C++

// result.h

typedef struct Result_Integer_ Result_Integer;
typedef struct Result_Float_ Result_Float;

Integer* Result_Integer_getResult(const Result_Integer* instance);
Float* Result_Float_getResult(const Result_Float* instance);

// data_processor.h

typedef struct DataProcessor_ DataProcessor;

Result_Integer* DataProcessor_processIntegerData(const DataProcessor* instance);
Result_Float* DataProcessor_processFloatData(constDataProcessor* instance);

Java

public class Result<T> {
  public T getResult();
}

public class DataProcessor {
  public Result<Integer> processIntegerData();
  public Result<Float> processFloatData();
}

實作介面

呼叫 implementInterface() 並為每種介面方法提供回呼函式,即可實作 C 介面。只有介面能以這種方式實作,類別和抽象類別皆不支援這種實作方式。請參閱以下範例:

C/C++

// observer.h

typedef struct Observer_ Observer;
typedef void (*Observer_onAction1Callback)();
typedef void (*Observer_onAction2Callback)(int32_t data);

Observer* Observer_implementInterface(
Observer_onAction1Callback observer_on_action1_callback,
Observer_onAction2Callback observer_on_action2_callback);

Java

public interface Observer {
  void onAction1();
  void onAction2(int data);
}

public class Subject {
  public void registerObserver(Observer observer);
}

使用範例:

void onAction1() {
  // Handle action 1
}

void onAction2(int32_t data) {
  // Handle action 2
}

Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);

限制

程式庫包裝函式工具目前處於 Beta 版測試階段,您可能會遇到以下限制:

不支援的 Java 建構項目

程式庫包裝函式 Beta 版不支援下列建構項目:

  • 方法超載

    C 語言不允許宣告兩個名稱相同的函式。如果類別使用方法超載,產生的 C 程式碼將不會編譯。解決方法為只使用一個方法搭配足夠的參數組合。您可以使用篩選器篩除其餘函式。這也適用於建構函式。

  • 範本式方法

  • static final intstatic final String 以外的欄位

  • 陣列

潛在名稱衝突

在極少數情況下,Java 類別在 C 程式碼中的表示方式可能會造成名稱衝突。例如在 C 中,類別 Foo<Bar> 和位於 Foo 類別中的內部類別 Bar 會以相同的符號表示:typedef struct Foo_Bar_ Foo_Bar;

支援

如果發現程式庫包裝函式有問題,請通知我們。

瀏覽錯誤 回報錯誤
工程
說明文件