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

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