Sematkan juru bahasa Python dalam aplikasi java menggunakan proyek Panama

Beberapa hari yang lalu saya melihat tweet dari Brian Goetz, tetapi hanya hari ini tangan saya mencapai untuk bermain dengan contoh.

gambar

Saya ingin berbicara singkat tentang ini.

Tentang proyek Panama di Habré sudah menulis . Untuk memahami apa itu dan mengapa, Anda harus membaca wawancara di sini. Saya hanya akan menunjukkan beberapa contoh sederhana tentang cara menggunakan binder asli .

Pertama-tama, Anda memerlukan kompiler C. Jika Anda menggunakan Linux atau MacOS, maka Anda sudah memilikinya. Jika Windows, pertama-tama Anda harus menginstal Alat Bangun untuk Visual Studio 2017 . Dan, tentu saja, Anda perlu OpenJDK dengan dukungan Panama. Anda bisa mendapatkannya dengan membangun cabang "asing" dari repositori yang sesuai , atau dengan mengunduh build Early-Access .

Mari kita mulai dengan contoh minimal - fungsi sederhana yang menambahkan dua angka:

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; } 

Kompilasi dalam DLL

 cl /LD adder.c 

Dan gunakan dalam kode java

 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)); } } 

Sumber harus memiliki banyak keakraban bagi mereka yang menggunakan JNR: antarmuka perpustakaan asli dinyatakan, perpustakaan dimuat, terkait dengan antarmuka, dan fungsi asli dipanggil. Perbedaan utama adalah penggunaan Layout Definition Language dalam anotasi untuk menggambarkan pemetaan data asli ke tipe Java.

Mudah ditebak bahwa ungkapan " (i32 i32)i32 " menunjukkan fungsi yang mengambil dua bilangan bulat 32-bit dan mengembalikan bilangan bulat 32-bit. Label i menunjukkan salah satu dari tiga tipe dasar - integer dengan urutan byte-endian kecil. Selain itu, u dan f sering ditemukan - bilangan bulat bertanda dan angka floating-point, masing-masing. Label yang sama digunakan untuk menunjukkan urutan big-endian, tetapi dalam huruf besar - I , U , F Label umum lainnya adalah v , digunakan untuk void . Nomor yang mengikuti label menunjukkan jumlah bit yang digunakan.

Jika nomornya sebelum label, maka label menunjukkan sebuah array: [42f32] - sebuah array dari 42 elemen tipe float . Label grup kurung kotak. Selain array, ini dapat digunakan untuk menunjukkan struktur ( [i32 i32] - struktur dengan dua bidang tipe int ) dan serikat pekerja ( [u64|u64:f32] - long atau penunjuk untuk float ).

Tanda titik dua digunakan untuk menunjukkan petunjuk. Sebagai contoh, u64:i32 adalah pointer ke int , u64:v adalah pointer tipe void , dan u64:[i32 i32] adalah pointer ke struktur.

Berbekal informasi ini, mari kita sedikit mempersulit contoh.

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)); } } } 

Beberapa elemen baru muncul dalam kode java sekaligus - Scope , Array , dan Pointer . Ketika bekerja dengan kode asli, Anda harus mengoperasikan data di luar tumpukan, yang berarti Anda harus mengalokasikan memori secara independen, melepaskan dan memantau relevansi pointer secara independen. Untungnya, ada Scope yang menangani semua masalah ini. Metode-metodenya membuatnya mudah dan nyaman untuk mengalokasikan memori yang tidak ditentukan, memori untuk array, struktur, dan garis-C, mendapatkan pointer ke memori ini, dan juga secara otomatis membebaskannya setelah blok coba sumber daya selesai dan mengubah status pointer yang dibuat sehingga panggilan itu menyebabkan pengecualian, bukan crash dari mesin virtual.

Untuk melihat pekerjaan struktur dan pointer, mari kita sedikitkan contohnya.

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()); } } } 

Yang menarik di sini adalah bagaimana antarmuka struktur dinyatakan dan bagaimana memori dialokasikan untuk itu. Perhatikan bahwa elemen baru telah muncul dalam deklarasi ldl - nilai dalam tanda kurung setelah label. Ini adalah anotasi tag dalam bentuk singkat. Bentuk lengkapnya akan terlihat seperti ini: i32(name=x) . Anotasi label memungkinkan Anda untuk menghubungkannya dengan metode antarmuka.

Sebelum beralih ke janji dalam judul, tetap menyoroti cara lain untuk berinteraksi dengan kode asli. Semua contoh sebelumnya disebut fungsi asli, tetapi terkadang kode asli perlu memanggil kode java. Misalnya, jika kita ingin mengurutkan array menggunakan qsort , kita perlu panggilan balik.

 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(); } } } 

Sangat mudah untuk melihat bahwa iklan LDL, yang tidak terlalu mudah dibaca, dengan cepat berubah menjadi desain yang marah. Tetapi qsort bukan fungsi yang paling sulit. Selain itu, mungkin ada lusinan struktur dan lusinan fungsi di perpustakaan nyata, menulis antarmuka bagi mereka adalah tugas yang tidak ada terima kasih. Untungnya, kedua masalah mudah diselesaikan dengan menggunakan utilitas jextract , yang akan menghasilkan semua antarmuka yang diperlukan berdasarkan file header. Mari kita kembali ke contoh pertama dan memprosesnya dengan utilitas ini.

 jextract -L . -l adder -o adder.jar -t "com.example" adder.h 

 //  jextract' ""  import static com.example.adder_h.*; public class Example { public static void main(String[] args) { System.out.println(add(3, 5)); } } 

Dan gunakan file jar yang dihasilkan untuk membangun dan menjalankan kode java:

 javac -cp adder.jar Example.java java -cp .;adder.jar Example 

Meskipun tidak terlalu mengesankan, tetapi memungkinkan Anda untuk memahami prinsipnya. Dan sekarang mari kita lakukan hal yang sama dengan python37.dll (akhirnya!)

 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(); } } 

Kami menghasilkan antarmuka:

 jextract -L "C:\Python37" -l python37 -o python.jar -t "org.python" --record-library-path C:\Python37\include\Python.h 

Kompilasi dan jalankan:

 javac -cp python.jar App.java java -cp .;python.jar App 

Selamat, aplikasi java Anda baru saja mengunduh interpreter Python dan mengeksekusi skrip di dalamnya!

gambar

Lebih banyak contoh dapat ditemukan dalam instruksi untuk perintis .

Proyek Maven dengan contoh-contoh dari artikel dapat ditemukan di GitHub .

API PS saat ini sedang mengalami perubahan cepat. Dalam presentasi beberapa bulan yang lalu, mudah untuk melihat kode yang tidak dapat dikompilasi. Contoh-contoh dari artikel ini tidak kebal dari ini. Jika Anda menemukan ini, kirim saya pesan, saya akan mencoba memperbaikinya.

Source: https://habr.com/ru/post/id440470/


All Articles