Wrapper ของไลบรารีสำหรับ Android API ส่วนหนึ่งของ Android Game Development Kit
Wrapper ของไลบรารีเป็นเครื่องมือบรรทัดคำสั่ง (CLI) ที่สร้างโค้ด Wrapper ภาษา C สำหรับ Android API ที่เขียนด้วย Java คุณใช้โค้ดนี้ในแอป Android เนทีฟเพื่อเรียกใช้ Java API ได้โดยไม่ต้องสร้าง Java Native Interface หรือ JNI ด้วยตนเอง เครื่องมือนี้ช่วยลดความซับซ้อนในการพัฒนา แอป Android ที่เขียนด้วยภาษา C หรือ C++ เป็นหลัก
เครื่องมือนี้ทำงานโดยสร้างโค้ด C สำหรับสัญลักษณ์สาธารณะที่มีอยู่ในไฟล์ Java Archive (JAR) ที่คุณระบุ หรือคลาสที่กำหนดไว้ในไฟล์กำหนดค่าของเครื่องมือ หรือทั้งสองอย่าง โค้ดที่เครื่องมือสร้างขึ้นจะไม่แทนที่ Java API แต่จะทำหน้าที่เป็นสะพานเชื่อมระหว่างโค้ด C กับ Java แอปของคุณยังคง ต้องมีไลบรารี Java ที่คุณรวมไว้ในโปรเจ็กต์
ดาวน์โหลด
ดาวน์โหลดที่เก็บไฟล์ Wrapper ของไลบรารีและแตกเนื้อหาไปยังไดเรกทอรี ที่คุณเลือก
วากยสัมพันธ์
เครื่องมือ Wrapper ไลบรารีมีไวยากรณ์บรรทัดคำสั่งต่อไปนี้
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 |
ไฟล์ JAR เพื่อสร้างโค้ด Wrapper ของ C คุณระบุ JAR ได้หลายรายการ
เช่น -i first_library.jar -i second_library.jar...
|
-o output-path |
ตำแหน่งระบบไฟล์สำหรับโค้ดที่สร้างขึ้น |
-c config-file |
เส้นทางระบบไฟล์ไปยังไฟล์การกำหนดค่า Wrapper ของไลบรารี โปรดดูรายละเอียดในส่วนการกำหนดค่า |
-fa allow-list-file |
เส้นทางไปยังไฟล์ตัวกรองที่คุณระบุสัญลักษณ์ให้เครื่องมือ ตัดคำได้ ดูรายละเอียดได้ที่ส่วนตัวกรอง |
-fb block-list-file |
เส้นทางไปยังไฟล์ตัวกรองที่มีสัญลักษณ์ที่ยกเว้นจากการตัดคำ ดู รายละเอียดได้ที่ส่วนตัวกรอง |
--skip_deprecated_symbols |
สั่งให้เครื่องมือ Wrapper ข้าม สัญลักษณ์ @Deprecated |
ไฟล์การกำหนดค่า Wrapper
การกำหนดค่า Wrapper ของไลบรารีคือไฟล์ 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
คุณใช้พารามิเตอร์ทั้ง 2 พร้อมกันได้ หากระบุตัวกรองทั้ง 2 รายการ ระบบจะห่อสัญลักษณ์เมื่อมีการกำหนดในไฟล์ตัวกรองที่อนุญาต
และไม่มีอยู่ในไฟล์ตัวกรองที่บล็อก
สถานการณ์ตัวอย่าง
สมมติว่าคุณต้องห่อไฟล์ JAR ChatLibrary.jar
ที่มีคลาสต่อไปนี้
public class ChatManager {
public static void sendMessage(int userId, String message) {...}
}
โปรเจ็กต์ C ของคุณกำหนดให้คุณสร้าง Wrapper เนทีฟสำหรับ JAR นี้ ซึ่งจะช่วยให้แอป Android เนทีฟเรียกใช้ได้ในระหว่างรันไทม์ สร้างโค้ดนี้โดยใช้ Wrapper ของไลบรารีด้วยคำสั่งต่อไปนี้
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);
ดูตัวอย่างสถานการณ์เชิงลึกได้ในคู่มือ Wrapper ของไลบรารี
รายละเอียดเครื่องมือ
ส่วนต่อไปนี้จะให้ข้อมูลโดยละเอียดเกี่ยวกับฟังก์ชันการทำงานของ Wrapper ของไลบรารี
โครงสร้างไดเรกทอรีเอาต์พุต
ไฟล์ต้นฉบับและไฟล์ส่วนหัวของ C ทั้งหมดจะอยู่ในไดเรกทอรีย่อยที่แสดง
ชื่อแพ็กเกจของคลาส Java ที่ห่อไว้ ตัวอย่างเช่น ระบบจะสร้างโค้ด Wrapper สำหรับ JAR java.lang.Integer
ที่ระบุไปยังไดเรกทอรี ./java/lang/integer.[h/cc]
คุณควบคุมลักษณะการทำงานของเอาต์พุตนี้ได้โดยใช้ไฟล์การกำหนดค่าของเครื่องมือ
วงจรของออบเจ็กต์
ออบเจ็กต์ Java จะแสดงในโค้ด C เป็นพอยน์เตอร์ทึบแสงที่เรียกว่า Wrapper Wrapper จัดการการอ้างอิง JNI สำหรับออบเจ็กต์ Java ที่เกี่ยวข้อง คุณสร้าง Wrapper ได้ในสถานการณ์ต่อไปนี้
- โดยการห่อหุ้มการอ้างอิง JNI ที่มีอยู่ด้วยการเรียกใช้ฟังก์ชัน
MyClass_wrapJniReference(jobject jobj)
ฟังก์ชัน ไม่ได้เป็นเจ้าของข้อมูลอ้างอิงที่ระบุ แต่จะสร้าง การอ้างอิง JNI ทั่วโลกของตัวเอง - การสร้างออบเจ็กต์ใหม่เทียบเท่ากับการเรียกตัวสร้างใน Java:
MyClass_construct()
- การแสดงผล Wrapper ใหม่จากฟังก์ชัน เช่น
Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)
คุณต้องทำลาย Wrapper ทั้งหมดเมื่อไม่ได้ใช้งานแล้ว โดยให้เรียกใช้destroy()
ฟังก์ชัน MyClass_destroy(MyClass* instance)
ที่กำหนดไว้
ฟังก์ชันที่แสดงผล Wrapper จะจัดสรรหน่วยความจำใหม่ให้ทุกครั้งที่มีการเรียกใช้ แม้ว่า Wrapper จะแสดงอินสแตนซ์ Java เดียวกันก็ตาม
ตัวอย่างเช่น เมื่อเมธอด Java Singleton.getInstance()
จะแสดงผลอินสแตนซ์เดียวกันเสมอ
ฟังก์ชันที่เทียบเท่าในฝั่ง C จะสร้างอินสแตนซ์ใหม่
ของ Wrapper สำหรับอินสแตนซ์ 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 ที่ระบุ Wrapper ของไลบรารีจะสร้างการใช้งานพื้นฐานซึ่งประกอบด้วยพอยน์เตอร์ทึบแสงและเมธอดต่อไปนี้
wrapJniReference()
getJniReference()
destroy()
รายละเอียดการสร้างโค้ด
เมื่อเรียกใช้ Wrapper ไลบรารีจะสร้างโค้ด C โดยอิงตามสัญลักษณ์สาธารณะในไฟล์ JAR ที่คุณระบุให้เครื่องมือ โค้ด C ที่สร้างขึ้นอาจมีความแตกต่าง จากโค้ด Java ที่ห่อไว้ เช่น C ไม่รองรับฟีเจอร์ต่างๆ เช่น OOP, ประเภททั่วไป การโอเวอร์โหลดเมธอด หรือฟีเจอร์อื่นๆ ของ Java
โค้ด C ที่สร้างขึ้นซึ่งแสดงถึงสถานการณ์เหล่านี้อาจแตกต่างจากประเภทของ โค้ดที่นักพัฒนา C คาดหวัง ตัวอย่างในส่วนต่อไปนี้จะให้ บริบทเกี่ยวกับวิธีที่เครื่องมืออาจสร้าง C จากโค้ด Java หมายเหตุ: ในข้อมูลโค้ด ตัวอย่างต่อไปนี้มีข้อมูลโค้ด C/C++ และ Java ข้อมูลโค้ดเหล่านี้มีไว้เพื่อแสดงให้เห็นวิธีที่เครื่องมือ สร้างโค้ดสำหรับแต่ละสถานการณ์ที่กำหนดเท่านั้น
ชั้นเรียน
คลาสจะแสดงเป็นพอยน์เตอร์แบบทึบใน C ดังนี้
C/C++
typedef struct MyClass_ MyClass;
Java
public class MyClass { ... }
อินสแตนซ์ของพอยน์เตอร์ทึบแสงจะอ้างอิงเป็นWrapper เครื่องมือ Wrapper
จะสร้างฟังก์ชันการสนับสนุนเพิ่มเติมสำหรับแต่ละคลาส สำหรับตัวอย่างก่อนหน้า
class 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) { ... }
}
}
ประเภททั่วไป
Wrapper ของไลบรารีไม่ได้ Wrapper ประเภททั่วไปโดยตรง แต่เครื่องมือจะสร้าง Wrapper สำหรับการสร้างอินสแตนซ์ประเภททั่วไปเท่านั้น
เช่น เมื่อมีคลาส MyGeneric<T>
ใน API และมีการสร้างอินสแตนซ์ของคลาสนี้ 2 รายการ เช่น MyGeneric<Integer>
และ MyGeneric<String>
ระบบจะสร้าง Wrapper สำหรับอินสแตนซ์ทั้ง 2 รายการนั้น ซึ่งหมายความว่าคุณไม่สามารถสร้างอินสแตนซ์ใหม่ของประเภท 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();
}
ใช้การเชื่อมต่อ
ใช้ C Interface โดยการเรียก implementInterface()
และระบุฟังก์ชัน
การเรียกกลับสำหรับแต่ละเมธอดของอินเทอร์เฟซ คุณจะใช้ได้เฉพาะอินเทอร์เฟซในลักษณะนี้เท่านั้น ระบบไม่รองรับคลาสและคลาสแบบนามธรรม โปรดดู
ตัวอย่างต่อไปนี้
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);
ข้อจำกัด
เครื่องมือ Library Wrapper อยู่ในเวอร์ชันเบต้า คุณอาจพบข้อจำกัดต่อไปนี้
โครงสร้าง Java ที่ไม่รองรับ
เบต้าของ Wrapper ไลบรารีไม่รองรับโครงสร้างต่อไปนี้
การโอเวอร์โหลดเมธอด
ภาษา C ไม่อนุญาตให้ประกาศฟังก์ชัน 2 รายการที่มีชื่อเดียวกัน หาก คลาสใช้การโอเวอร์โหลดเมธอด โค้ด C ที่สร้างขึ้นจะคอมไพล์ไม่ได้ วิธีแก้คือการใช้วิธีเดียวที่มีชุดพารามิเตอร์เพียงพอ คุณกรองฟังก์ชันที่เหลือออกได้โดยใช้ตัวกรอง ซึ่งรวมถึงตัวสร้างด้วย
วิธีการที่ใช้เทมเพลต
ฟิลด์อื่นๆ นอกเหนือจาก
static final int
และstatic final String
อาร์เรย์
ชื่ออาจซ้ำกัน
เนื่องจากลักษณะการแสดงคลาส Java ในโค้ด C จึงอาจเกิดความขัดแย้งของชื่อในบางกรณีที่พบได้ไม่บ่อยนัก ตัวอย่างเช่น คลาส Foo<Bar>
และคลาสภายใน
Bar
ภายในคลาส Foo
จะแสดงด้วยสัญลักษณ์เดียวกันใน C:
typedef struct Foo_Bar_ Foo_Bar;
การสนับสนุน
หากพบปัญหาเกี่ยวกับ Wrapper ของไลบรารี โปรดแจ้งให้เราทราบ
เรียกดูข้อบกพร่อง | รายงานข้อบกพร่อง |
---|---|
วิศวกรรม | bug_report |
เอกสารประกอบ | bug_report |