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

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

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

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

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

لا يضفي برنامج تضمين المكتبة أنواعًا عامة مباشرةً. بدلاً من ذلك، لا تنشئ الأداة إلا برامج تضمين للنسخ الافتراضية للأنواع العامة.

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

القيود

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

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

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

  • حِمل طريقة الدفع

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

  • الطرق المستنِدة إلى نموذج

  • الحقول الأخرى بخلاف static final int وstatic final String

  • الصفائف

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

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

الدعم

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

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