適用於 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 int
和static final String
以外的欄位陣列
潛在名稱衝突
在極少數情況下,Java 類別在 C 程式碼中的表示方式可能會造成名稱衝突。例如在 C 中,類別 Foo<Bar>
和位於 Foo
類別中的內部類別 Bar
會以相同的符號表示:typedef struct Foo_Bar_ Foo_Bar;
支援
如果發現程式庫包裝函式有問題,請通知我們。
瀏覽錯誤 | 回報錯誤 |
---|---|
工程 | bug_report |
說明文件 | bug_report |