API مربوط به Foreign Function and Memory میتواند در فراخوانی کد بومی (Native) به شما کمک کند. در این آموزش، نحوهی فراخوانی یک تابع کتابخانهی C از زبان جاوا را بررسی میکنیم.
تابع استاندارد strdup در زبان C را در نظر بگیرید:
char *strdup(const char *s);
این تابع یک آرگومان دریافت میکند — یک رشتهی s — و یک اشارهگر (Pointer) به یک رشتهی بایتی ختمشده با null برمیگرداند.
نوع بازگشتی این تابع، نسخهی تکراری رشتهای است که s به آن اشاره میکند. برای فراخوانی strdup از یک برنامهی جاوا، باید مراحل زیر را دنبال کنید:
strdup تخصیص دهید و رشتهی جاوا را در آن حافظه ذخیره کنید.strdup اشاره میکند بسازید و آن را فراخوانی کنید.میتوانید با استفاده از MemorySegment و یک Arena محدود (Confined) حافظهی Off-Heap تخصیص دهید و رشتهی جاوا را در آن کپی کنید.
String pattern = "********";
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeString = arena.allocateFrom(pattern);
}
حال ببینیم چگونه MemorySegment را به تابع بومی (Native) منتقل کنیم.
برای دسترسی به کتابخانهی C بومی، میتوانید از Native Linker استفاده کنید. این لینکر دو نقش ایفا میکند: دسترسی به توابع خارجی (Foreign Functions) از کد جاوا و دسترسی به کد جاوا از توابع خارجی.
Linker linker = Linker.nativeLinker();
برای فراخوانی یک متد بومی مثل strdup، به یک Downcall Method Handle نیاز دارید. این یک نمونه از MethodHandle است که به یک تابع بومی اشاره میکند.
برای ساخت این نمونه به آدرس تابع بومی نیاز است.
روشهای مختلفی برای پیدا کردن آدرس یک تابع C وجود دارد:
Linker.defaultLookup().SymbolLookup.loaderLookup(String) میتوانید نمادها را در کتابخانههایی که با System.loadLibrary(String) بارگذاری شدهاند جستجو کنید.SymbolLookup.libraryLookup(String, Arena) میتوانید از نام یک کتابخانه، یک Symbol Lookup بسازید.// libc.so.6 is available on unix operating systems
SymbolLookup stdLib = SymbolLookup.libraryLookup("libc.so.6", arena);
MemorySegment strdup_addr = stdLib.find("strdup").get();
متد SymbolLookup.libraryLookup(String, Arena) کتابخانهی مشخصشده را بارگذاری میکند و آن را با یک Arena مرتبط میسازد. Arena عمر Symbol Lookup را کنترل میکند.
چون strdup بخشی از کتابخانهی استاندارد C است، نیازی به مشخص کردن نام فایل وابسته به سیستمعامل نیست و میتوانید از Lookup پیشفرض Native Linker استفاده کنید:
// Locate the address of the C function signature
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strdup_addr = stdLib.find("strdup").get();
حال که آدرس تابع C را پیدا کردیم، ببینیم امضای آن چگونه به جاوا ترجمه میشود.
Downcall Method Handle به توصیف امضای تابع بومی نیز نیاز دارد. این توصیف توسط یک نمونه از FunctionDescriptor بیان میشود.
یک Function Descriptor، لایوتهای آرگومانها و مقدار بازگشتی تابع بومی (در صورت وجود) را نشان میدهد.
هر لایوت در یک Function Descriptor به یک نوع جاوا نگاشت میشود. این نوع همان چیزی است که هنگام فراخوانی Downcall Method Handle به آن نیاز دارید.
بیشتر Value Layout ها به یک نوع اولیه (Primitive) جاوا نگاشت میشوند. برای مثال، ValueLayout.JAVA_FLOAT به مقدار float نگاشت میشود.
هشدار: نوعهای اولیهی بومی با استفاده از Value Layout هایی مدلسازی میشوند که اندازهشان با اندازهی آن نوعها برابر است. اما Function Descriptor وابسته به پلتفرم است. برای مثال،
size_tروی پلتفرمهای 64 بیتی لایوتJAVA_LONGدارد، ولی روی پلتفرمهای 32 بیتی لایوتJAVA_INTدارد.
اگر میخواهید لایوت یک نوع اولیهی بومی را که Native Linker برای پلتفرم شما استفاده میکند بشناسید، متد Linker.canonicalLayouts() را فراخوانی کنید.
آرگومانهای بعدی FunctionDescriptor.of(MemoryLayout,MemoryLayout...) لایوتهای آرگومانهای تابع بومی هستند.
هنگام استفاده از اشارهگرها (Pointers) بهعنوان آرگومان یا نوع بازگشتی، باید از ValueLayout.ADDRESS استفاده کنید.
نوعهای ترکیبی مثل Struct و Union با Interface به نام GroupLayout مدلسازی میشوند. این Interface والد StructLayout و UnionLayout است.
برای ساخت یک FunctionDescriptor برای char *strdup(const char *s) به موارد زیر نیاز دارید:
ValueLayout.ADDRESS نیاز دارید که بتواند رشتهی بایتی را نشان دهد.
برای مطابقت با لایوت رشتهی بایتی، میتوانید از MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE) استفاده کنید. پارامتر اول تعداد عناصر دنباله و پارامتر دوم لایوت هر عنصر است.با استفاده از عناصر بالا، اکنون میتوانید Function Descriptor را برای تابع strdup بسازید:
var layout = MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE);
FunctionDescriptor strdup_sig = FunctionDescriptor.of(
ValueLayout.ADDRESS.withTargetLayout(layout),
ValueLayout.ADDRESS.withTargetLayout(layout)
);
برای ساخت یک Downcall Method Handle برای تابع strdup باید آدرس و Function Descriptor آن را به صورت زیر پاس دهید:
MethodHandle strdup_handle = linker.downcallHandle(strdup_addr, strdup_sig);
مرحلهی نهایی، فراخوانی Method Handle با MemorySegment حاوی آرگومان تابع است:
String pattern = "********";
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeString = arena.allocateFrom(pattern);
// ...
MemorySegment duplicatedAddress = (MemorySegment) strdup_handle.invokeExact(nativeString);
}
هشدار: متد
MethodHandle.invokeExact(Object...)هر نوع توصیفی را از caller میپذیرد، اما تطابق دقیق نوع را الزامی میکند. توصیف نوع نمادین هنگام فراخوانیinvokeExactباید دقیقاً با نوع این Method Handle مطابقت داشته باشد.
برای دسترسی به کل مقدار رشتهی تکرارشده، میتوانید MemorySegment.getString(offset) را فراخوانی کنید که در آن مقدار offset برابر 0 است.
String pattern = "********";
try (Arena arena = Arena.ofConfined()) {
MemorySegment nativeString = arena.allocateFrom(pattern);
// ...
MemorySegment duplicatedAddress = (MemorySegment) strdup_handle.invokeExact(nativeString);
duplicatedAddress.getString(0);
}
بیایید همهی مراحل بالا را در یک قطعه کد که یک مثلث چاپ میکند جمعبندی کنیم:
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
public class NativeTriangle {
public static void main(String[] args) throws Throwable {
String pattern = (args.length > 0) ? args[0] : "********";
invokeStrdup(pattern);
}
private static void invokeStrdup(String pattern) throws Throwable {
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory and
// copy the argument, a Java string, into off-heap memory
MemorySegment nativeString = arena.allocateFrom(pattern);
// Obtain an instance of the native linker
Linker linker = Linker.nativeLinker();
// Locate the address of the C function signature
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strdup_addr = stdLib.find("strdup").get();
// Create a description of the C function
var layout = MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE);
FunctionDescriptor strdup_sig = FunctionDescriptor.of(
ValueLayout.ADDRESS.withTargetLayout(layout),
ValueLayout.ADDRESS.withTargetLayout(layout)
);
// Create a downcall handle for the C function
MethodHandle strdup_handle = linker.downcallHandle(strdup_addr, strdup_sig);
// Call the C function directly from Java
MemorySegment duplicatedAddress = (MemorySegment) strdup_handle.invokeExact(nativeString);
for (int i = pattern.length() - 1; i >= 0; i--) {
IO.println(duplicatedAddress.getString(i));
}
}
}
}
برای اجرای این برنامه از خط فرمان استفاده کنید:
java NativeTriangle.java
برای یادگیری عمیقتر، ویدیوی زیر را مشاهده کنید:
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/