منذ يومين رأيت تغريدة من برايان جويتز ، لكن اليوم فقط وصلت يدي للعب مع أمثلة.

أريد أن أتحدث بإيجاز عن هذا.
حول
مشروع بنما على حبري
كتب بالفعل . لفهم ما هو ولماذا ، يجب عليك قراءة المقابلة هنا. سأعرض فقط بضعة أمثلة بسيطة حول كيفية استخدام
الرابط الأصلي .
بادئ ذي بدء ، أنت بحاجة إلى برنامج التحويل البرمجي C. إذا كنت تستخدم Linux أو MacOS ، فأنت لديك بالفعل برنامج. إذا كان Windows ، سيكون عليك أولاً تثبيت
Build Tools لـ Visual Studio 2017 . وبالطبع ، أنت بحاجة إلى OpenJDK بدعم من بنما. يمكنك الحصول عليها إما عن طريق إنشاء فرع "أجنبي"
لمستودع التخزين المقابل ، أو عن طريق تنزيل
بنية الدخول المبكر .
لنبدأ بمثال بسيط - وظيفة بسيطة تضيف رقمين:
adder.h#ifndef _ADDER_H #define _ADDER_H __declspec(dllexport) int add(int, int); #endif
adder.c #include "adder.h" __declspec(dllexport) int add(int a, int b) { return a + b; }
ترجمة في DLL
cl /LD adder.c
واستخدامها في كود جافا
import java.foreign.Library; import java.foreign.Libraries; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface Adder { @NativeFunction("(i32 i32)i32") int add(int a, int b); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "adder"); Adder adder = Libraries.bind(Adder.class, lib); System.out.println(adder.add(3, 5)); } }
يجب أن يكون للمصدر الكثير من الأمور المألوفة بالنسبة لأولئك الذين يستخدمون JNR: يتم الإعلان عن واجهة المكتبة الأصلية ، ويتم تحميل المكتبة ، وترتبط بالواجهة ، وتسمى الوظيفة الأصلية. يتمثل الاختلاف الرئيسي في استخدام
لغة تعريف التخطيط في التعليقات التوضيحية لوصف تعيين البيانات الأصلية على أنواع Java.
من السهل تخمين أن تعبير "
(i32 i32)i32
" يشير إلى دالة تأخذ عدد صحيحين 32 بت وإرجاع عدد صحيح 32 بت. يشير Label
i
إلى واحد من ثلاثة أنواع أساسية - عدد صحيح بترتيب بايت صغير. بالإضافة إلى ذلك ، غالبًا ما يتم العثور على
u
و
f
- عدد صحيح غير موقّع ورقم الفاصلة العائمة ، على التوالي. يتم استخدام نفس الملصقات للإشارة إلى ترتيب endian الكبير ، ولكن في الحالة العليا -
I
،
U
،
F
تسمية شائعة أخرى هي
v
، تستخدم
void
. يشير الرقم الذي يلي التسمية إلى عدد وحدات البت المستخدمة.
إذا كان الرقم قبل التسمية ،
[42f32]
إلى صفيف:
[42f32]
- صفيف مكون من 42 عنصرًا من النوع
float
. تسميات مجموعة الأقواس المربعة. بالإضافة إلى المصفوفات ، يمكن استخدام هذا للإشارة إلى الهياكل (
[i32 i32]
- هيكل به حقلان من النوع
int
) والاتحادات (
[u64|u64:f32]
-
long
أو مؤشر
float
).
يتم استخدام النقطتين للإشارة إلى المؤشرات. على سبيل المثال ،
u64:i32
هو مؤشر إلى
int
،
u64:v
هو مؤشر type type ، و
u64:[i32 i32]
هو مؤشر إلى بنية.
مسلحين بهذه المعلومات ، دعنا نعقد المثال قليلاً.
totalizer.c __declspec(dllexport) long sum(int vals[], int size) { long r = 0; for (int i = 0; i < size; i++) { r += vals[i]; } return r; }
App.java import java.foreign.Library; import java.foreign.Libraries; import java.foreign.NativeTypes; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.memory.Array; import java.foreign.memory.Pointer; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface Totalizer { @NativeFunction("(u64:i32 i32)u64") long sum(Pointer<Integer> vals, int size); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "totalizer"); Totalizer totalizer = Libraries.bind(Totalizer.class, lib); try (Scope scope = Scope.newNativeScope()) { Array<Integer> array = scope.allocateArray(NativeTypes.INT, new int[] { 23, 15, 4, 16, 42, 8 }); System.out.println(totalizer.sum(array.elementPointer(), 3)); } } }
ظهرت عدة عناصر جديدة في شفرة java في وقت واحد -
Scope
Array
Pointer
. عند العمل باستخدام الكود الأصلي ، سيتعين عليك تشغيل بيانات خارج الكومة ، مما يعني أنه سيتعين عليك تخصيص الذاكرة بشكل مستقل ، وإطلاق ومراقبة أهمية المؤشرات بشكل مستقل. لحسن الحظ ، هناك
Scope
يعتني بكل هذه المخاوف. تسهِّل طرقه تسهيل تخصيص ذاكرة غير محددة وذاكرة للصفائف والهياكل وخطوط C ، والحصول على مؤشرات لهذه الذاكرة ، وكذلك تحريرها تلقائيًا بعد اكتمال كتلة try-with-resources وتغيير حالة المؤشرات التي تم إنشاؤها أدى إلى استثناء ، وليس تعطل الجهاز الظاهري.
لننظر إلى عمل الهياكل والمؤشرات ، دعنا نجعل المثال أكثر تعقيدًا.
mover.h #ifndef _ADDER_H #define _ADDER_H typedef struct { int x; int y; } Point; __declspec(dllexport) void move(Point*); #endif
mover.c #include "mover.h" __declspec(dllexport) void move(Point *point) { point->x = 4; point->y = 2; }
App.java import java.foreign.Library; import java.foreign.Libraries; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.annotations.NativeStruct; import java.foreign.annotations.NativeGetter; import java.foreign.memory.LayoutType; import java.foreign.memory.Pointer; import java.foreign.memory.Struct; import java.lang.invoke.MethodHandles; public class App { @NativeStruct("[i32(x) i32(y)](Point)") interface Point extends Struct<Point> { @NativeGetter("x") int x(); @NativeGetter("y") int y(); } @NativeHeader interface Mover { @NativeFunction("(u64:[i32 i32])v") void move(Pointer<Point> point); } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "mover"); Mover mover = Libraries.bind(Mover.class, lib); try (Scope scope = Scope.newNativeScope()) { Pointer<Point> point = scope.allocate( LayoutType.ofStruct(Point.class)); mover.move(point); System.out.printf("X: %d Y: %d%n", point.get().x(), point.get().y()); } } }
المهم هنا هو كيف يتم الإعلان عن واجهة الهيكل وكيف يتم تخصيص الذاكرة لها. لاحظ أنه قد ظهر عنصر جديد في إعلان ldl - القيم الموجودة بين قوسين بعد التسميات. هذا تعليق توضيحي في شكل مختصر. الشكل الكامل سيبدو كما يلي:
i32(name=x)
. يسمح لك التعليق التوضيحي بالترابط مع طريقة الواجهة.
قبل الانتقال إلى الوعد في العنوان ، يبقى إبراز طريقة أخرى للتفاعل مع الكود الأصلي. تسمى جميع الأمثلة السابقة بالوظائف الأصلية ، ولكن في بعض الأحيان يحتاج الكود الأصلي إلى استدعاء java code. على سبيل المثال ، إذا كنا نريد فرز صفيف باستخدام
qsort
، فنحن بحاجة إلى رد اتصال.
import java.foreign.Library; import java.foreign.Libraries; import java.foreign.NativeTypes; import java.foreign.Scope; import java.foreign.annotations.NativeHeader; import java.foreign.annotations.NativeFunction; import java.foreign.annotations.NativeCallback; import java.foreign.memory.Array; import java.foreign.memory.Callback; import java.foreign.memory.Pointer; import java.lang.invoke.MethodHandles; public class App { @NativeHeader interface StdLib { @NativeFunction("(u64:[0i32] i32 i32 u64:(u64:i32 u64:i32) i32)v") void qsort(Pointer<Integer> base, int nitems, int size, Callback<QComparator> comparator); @NativeCallback("(u64:i32 u64:i32)i32") interface QComparator { int compare(Pointer<Integer> p1, Pointer<Integer> p2); } } public static void main(String[] args) { Library lib = Libraries.loadLibrary(MethodHandles.lookup(), "msvcr120"); StdLib stdLib = Libraries.bind(StdLib.class, lib); try (Scope scope = Scope.newNativeScope()) { Array<Integer> array = scope.allocateArray(NativeTypes.INT, new int[] { 23, 15, 4, 16, 42, 8 }); Callback<StdLib.QComparator> cb = scope.allocateCallback( (p1, p2) -> p1.get() - p2.get()); stdLib.qsort(array.elementPointer(), (int) array.length(), Integer.BYTES, cb); for (int i = 0; i < array.length(); i++) { System.out.printf("%d ", array.get(i)); } System.out.println(); } } }
من السهل أن نرى أن إعلانات ldl ، التي ليست سهلة القراءة بشكل خاص ، تتحول بسرعة إلى تصميمات غاضبة. لكن
qsort
ليست أصعب وظيفة. بالإضافة إلى ذلك ، يمكن أن يكون هناك العشرات من الهياكل وعشرات الوظائف في المكتبات الحقيقية ، وكتابة واجهات لهم هي مهمة ناكر للجميل. لحسن الحظ ، يمكن حل كلتا المشكلتين بسهولة باستخدام الأداة المساعدة
jextract
، والتي ستنشئ جميع الواجهات الضرورية استنادًا إلى ملفات الرأس. دعنا نعود إلى المثال الأول ومعالجته مع هذه الأداة.
jextract -L . -l adder -o adder.jar -t "com.example" adder.h
واستخدم ملف jar الناتج لإنشاء وتشغيل java code:
javac -cp adder.jar Example.java java -cp .;adder.jar Example
في حين ليست مثيرة للإعجاب بشكل خاص ، ولكن يسمح لك بفهم المبدأ. والآن دعونا نفعل الشيء نفسه مع python37.dll (أخيرًا!)
import java.foreign.Scope; import java.foreign.memory.Pointer; import static org.python.Python_h.*; import static org.python.pylifecycle_h.*; import static org.python.pythonrun_h.*; public class App { public static void main(String[] args) { Py_Initialize(); try (Scope s = Scope.newNativeScope()) { PyRun_SimpleStringFlags( s.allocateCString("print(sum([23, 15, 4, 16, 42, 8]))\n"), Pointer.nullPointer()); } Py_Finalize(); } }
نحن نولد واجهات:
jextract -L "C:\Python37" -l python37 -o python.jar -t "org.python" --record-library-path C:\Python37\include\Python.h
تجميع وتشغيل:
javac -cp python.jar App.java java -cp .;python.jar App
تهانينا ، لقد قام تطبيق java بتنزيل مترجم Python وقام بتنفيذ البرنامج النصي فيه!
يمكن العثور على المزيد من الأمثلة في
تعليمات الرواد .
يمكن العثور
على مشاريع مخضرمة مع أمثلة من المقال
على جيثب .
PS API يخضع حاليا لتغيرات سريعة. في العروض التقديمية التي تمت قبل شهرين ، من السهل رؤية الشفرة التي لن يتم تجميعها. الأمثلة من هذه المقالة ليست محصنة من هذا. إذا واجهت هذا ، أرسل لي رسالة ، سأحاول إصلاحه.