Wrapper ของไลบรารีสำหรับ API ของ Android เป็นส่วนหนึ่งของชุดเครื่องมือพัฒนาเกม Android

Wrapper ของไลบรารีคือเครื่องมือบรรทัดคำสั่ง (CLI) ที่สร้างภาษา C โค้ด Wrapper สำหรับ API ของ Android ที่เขียนด้วย Java คุณใช้ โค้ดในแอป Android ที่มาพร้อมเครื่องเพื่อเรียกใช้ Java API ได้โดยไม่จำเป็นต้องสร้างโค้ด สร้าง Java Native Interface หรือ JNI เครื่องมือนี้สามารถทำให้การพัฒนาง่ายขึ้น แอป Android ที่เขียนด้วยภาษา C หรือ C++ เป็นหลัก

เครื่องมือจะทำงานโดยสร้างโค้ด C สำหรับสัญลักษณ์สาธารณะที่อยู่ใน ไฟล์ Java Archive (JAR) ที่คุณระบุ หรือคลาสที่กำหนดไว้ในไฟล์ หรือทั้ง 2 ไฟล์ก็ได้ โค้ดที่เครื่องมือสร้างขึ้นไม่ได้แทนที่ 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 เพื่อสร้างโค้ด C Wrapper สามารถระบุ 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 ข้าม @เลิกใช้งานแล้ว สัญลักษณ์

ไฟล์การกำหนดค่า 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 รายการใช้พร้อมกันได้ หากตัวกรองทั้งสอง ระบบจะรวมสัญลักษณ์ไว้เมื่อมีการกำหนดไว้ในไฟล์ตัวกรองอนุญาต และไม่อยู่ในไฟล์ตัวกรองการบล็อก

สถานการณ์ตัวอย่าง

สมมติว่าคุณต้องตัดไฟล์ 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]

คุณสามารถควบคุมลักษณะการทํางานของเอาต์พุตนี้ได้โดยใช้เครื่องมือ configuration

วงจรของออบเจ็กต์

ออบเจ็กต์ 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 เดียวกัน

ตัวอย่างเช่น เมื่อ Method ของ 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 จะสร้างฟังก์ชันการสนับสนุนเพิ่มเติมสำหรับแต่ละคลาส สำหรับตัวอย่างก่อนหน้านี้ คลาส 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 สำหรับการสร้างอินสแตนซ์ประเภททั่วไปเท่านั้น

ตัวอย่างเช่น เมื่อมีคลาส 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 โดยเรียกใช้ implementInterface() และระบุ ฟังก์ชัน Callback สำหรับอินเทอร์เฟซแต่ละวิธี ติดตั้งใช้งานได้เฉพาะอินเทอร์เฟซเท่านั้น ในลักษณะนี้ ระบบไม่รองรับชั้นเรียนและคลาสนามธรรม โปรดดู ตัวอย่างต่อไปนี้

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);

ข้อจำกัด

เครื่องมือ 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 ของไลบรารี โปรดแจ้งให้เราทราบ

เรียกดูข้อบกพร่อง รายงานข้อบกพร่อง
วิศวกรรม
เอกสารประกอบ