برنامج تضمين المكتبة لواجهات برمجة تطبيقات Android جزء من مجموعة تطوير ألعاب Android.

برنامج تضمين المكتبة هو أداة سطر أوامر (CLI) تنشئ رمز برنامج تضمين بلغة C لواجهات برمجة تطبيقات Android التي تتم كتابتها بلغة Java. ويمكنك استخدام هذا الرمز في تطبيقات Android الأصلية لطلب واجهات برمجة تطبيقات Java بدون الحاجة إلى إنشاء Java Native Interface أو JNI يدويًا. يمكن لهذه الأداة تبسيط تطوير تطبيقات Android المكتوبة بشكل أساسي بلغة C أو C++.

تعمل الأداة عن طريق إنشاء رمز C للرموز العامة الموجودة في ملفات أرشيف Java (JAR) التي تقدمها، أو الفئات المحددة في ملف إعداد الأداة، أو كليهما. التعليمة البرمجية التي تم إنشاؤها بواسطة الأداة لا تحل محل واجهات برمجة تطبيقات Java؛ وبدلاً من ذلك، فإنها تعمل كجسر بين التعليمة البرمجية 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 JAR لإنشاء رمز برنامج تضمين C. يمكن تحديد عدة ملفات 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 توجِّه هذه العلامة إلى أداة التضمين لتخطّي رموز @متوقفة.

ملف إعداد برنامج الالتفاف

إعدادات برنامج تضمين المكتبة هي ملف 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].

يمكنك التحكّم في سلوك الإخراج هذا باستخدام ملف الإعداد في الأداة.

مراحل نشاط العنصر

يتم تمثيل كائنات Java في التعليمة البرمجية C كمؤشرات مبهمة، تسمى برامج تضمين. يدير برنامج تضمين مرجع JNI لكائن Java مطابق. يمكن إنشاء برنامج تضمين في السيناريوهات التالية:

  • من خلال إحاطة مرجع JNI الحالي باستدعاء الدالة MyClass_wrapJniReference(jobject jobj). لا تحصل الدالة على ملكية المرجع المقدم، ولكنها تنشئ مرجع 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()

تفاصيل إنشاء الرمز

عند تشغيل برنامج تضمين المكتبة، يُنشئ رمز C استنادًا إلى الرموز العامة في ملفات JAR التي تقدمها للأداة. قد يظهر رمز C الذي تم إنشاؤه اختلافات عن رمز Java الذي تم لفّه. على سبيل المثال، لا تتيح الفئة C استخدام ميزات مثل OOP أو الأنواع العامة أو التحميل الزائد للطريقة أو ميزات Java الأخرى.

قد يختلف رمز C الذي تم إنشاؤه ويعكس هذه المواقف عن نوع الكود الذي يتوقعه مطورو C. توفر الأمثلة الواردة في الأقسام التالية سياقًا حول كيفية إنشاء الأداة لـ C من كود Java. ملاحظة: في مقتطفات الرمز، تتضمّن الأمثلة التالية مقتطفات رمز C/C++ وJavaScript. وتهدف هذه المقتطفات فقط إلى توضيح طريقة إنشاء الأداة للرموز البرمجية في كل حالة.

الفئات

يتم تمثيل الفئات على أنّها مؤشرات مبهمة في لغة 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) { ... }
}

الطرق

يتم تمثيل الطرق كدوال عادية. يحتوي اسم الدالة على اسم الفئة الأصلية. الدوال التي تمثل طرق المثيل غير الثابتة هي المعلمة الأولى مؤشر إلى بنية تمثل كائن جافا، وتسمى الدالة بالنيابة عنها. يشبه هذا الأسلوب مؤشّر 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) { ... }
  }
}

الأنواع العامة

لا يدمج برنامج تضمين المكتبة الأنواع العامة بشكل مباشر. بدلاً من ذلك، تقوم الأداة فقط بإنشاء برامج تضمين للمثيلات العامة للنوع.

على سبيل المثال، عند توفُّر فئة 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();
}

تنفيذ الواجهات

تنفيذ واجهة C من خلال استدعاء 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);

القيود

تتوفّر أداة برنامج تضمين المكتبة في إصدار تجريبي. قد تواجه القيود التالية:

بُنى Java غير متوافقة

لا يدعم الإصدار التجريبي من برنامج تضمين المكتبة التركيبات التالية:

  • التحميل الزائد للطريقة

    لا تسمح اللغة C بإعلان دالتين بنفس الاسم. إذا كانت الفئة تستخدم طريقة حمولة زائدة، فلن يتم تجميع التعليمة البرمجية C التي تم إنشاؤها. الحل البديل هو استخدام طريقة واحدة فقط مع مجموعة كافية من المعلمات. يمكن فلترة الدوال المتبقية باستخدام الفلاتر. ينطبق هذا أيضًا على الدوال الإنشائية.

  • طرق نموذجية

  • حقول أخرى غير static final int وstatic final String

  • الصفائف

هناك تعارضات محتملة بين الأسماء.

نظرًا لكيفية تمثيل فئات Java في التعليمات البرمجية C، قد يكون هناك تعارضات في الأسماء في حالات نادرة جدًا. على سبيل المثال، يتم تمثيل الفئة Foo<Bar> والفئة الداخلية Bar في الفئة Foo بالرمز نفسه في لغة C: typedef struct Foo_Bar_ Foo_Bar;

الدعم

إذا واجهتك مشكلة في برنامج تضمين المكتبة، يُرجى إعلامنا.

تصفُّح الأخطاء الإبلاغ عن خطأ
الهندسة
الوثائق