برنامج تضمين المكتبة لواجهات برمجة تطبيقات Android جزء من Android Game Development Kit
أداة تضمين المكتبة هي أداة سطر أوامر (CLI) تنشئ رمز تضمين بلغة C لواجهات برمجة تطبيقات Android المكتوبة بلغة Java. يمكنك استخدام هذا الرمز في تطبيقات Android الأصلية لاستدعاء واجهات برمجة تطبيقات Java بدون الحاجة إلى إنشاء واجهة Java الأصلية أو JNI يدويًا. يمكن أن تسهّل هذه الأداة عملية تطوير تطبيقات Android المكتوبة بشكل أساسي بلغة C أو C++.
تعمل الأداة من خلال إنشاء رمز C للرموز العامة الواردة في ملفات Java Archive (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 |
توجّه أداة التغليف إلى تجاهل الرموز التي تحمل التعليق التوضيحي @Deprecated. |
ملف إعداد برنامج التغليف
إعدادات برنامج تضمين المكتبة هي ملف 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);
القيود
تتوفّر أداة تضمين المكتبة في إصدار تجريبي. قد تواجه القيود التالية:
بُنى Java غير المتوافقة
لا تتوافق النسخة التجريبية من برنامج تضمين المكتبة مع البُنى التالية:
تحميل الأساليب الزائد
لا تسمح لغة C بتعريف دالتَين بالاسم نفسه. إذا كان الصف يستخدم تحميلًا زائدًا للطريقة، لن يتم تجميع رمز C الذي تم إنشاؤه. الحل البديل هو استخدام طريقة واحدة فقط مع مجموعة كافية من المَعلمات. يمكن فلترة الدوال المتبقية باستخدام الفلاتر. وينطبق ذلك أيضًا على الدوال الإنشائية.
الطرق المستندة إلى نماذج
الحقول غير
static final int
وstatic final String
المصفوفات
احتمالات حدوث تضاربات في الأسماء
بسبب طريقة تمثيل فئات Java في رمز C، قد تحدث تعارضات في الأسماء في حالات نادرة جدًا. على سبيل المثال، يتم تمثيل الفئة Foo<Bar>
والفئة الداخلية Bar
داخل الفئة Foo
بالرمز نفسه في C:
typedef struct Foo_Bar_ Foo_Bar;
الدعم
إذا واجهت مشكلة في برنامج تضمين المكتبة، يُرجى إعلامنا بذلك.
تصفُّح الأخطاء | الإبلاغ عن خطأ |
---|---|
الهندسة | bug_report |
المستندات | bug_report |