Java 3'te C kitaplıklarını kullanma: Karmaşık uygulama, tuzaklar ve en iyi uygulamalar

kapanış bildirimi

Bu makale İngilizce olarak da mevcuttur. Teknik yardımla tercüme edildi ve yayınlanmadan önce editoryal olarak gözden geçirildi.

Java'nın Yabancı İşlev ve Bellek (FFM) API'si, paylaşılan bir kitaplıktaki koda veya C veya Rust gibi bir programlama dilinde yazılmış DLL'ye erişmek için kullanılır. Ancak kodun bazı gereksinimleri karşılaması gerekir.

Duyurudan sonra devamını okuyun

Rudolf Ziegaus bir yazılım geliştiricisi, Java eğitmeni ve IO Software GmbH'nin CEO'sudur. En sevdiği konular PKi, kriptografi ve düşük seviyeli programlamadır.

Bu üç bölümlük makale serisi, bir Java uygulamasının kitaplığın işlevlerini nasıl çağırdığını, hangi hazırlıkların gerekli olduğunu ve hangi kurallara uyulması gerektiğini göstermek için C dilinde yazılmış bir demo kitaplığı kullanır.

İlk iki bölümde FFM API aracılığıyla yerel Java koduna erişmeye yönelik en önemli terimler ve teknikler gösterildikten sonra, bu üçüncü ve son bölüm dikkate alınması gereken bazı uzmanlıkları kapsamaktadır.

Tamamlanmayan başvurular var MemorySegment kullanmalısınız, ancak yalnızca bir kısmını kullanmalısınız; örneğin, size bir liste verildiyse ancak yalnızca bir kısmına ihtiyacınız varsa. O halde, her zaman dizinin tamamına erişmeniz gerekmiyorsa pratiktir, ancak bellek alanı üzerinde bir tür görünüm oluşturabilirsiniz – yöntemin yaptığı tam olarak budur asSlice. Segmentten bir alanı keser ve o alan için yeni bir segment sağlar.

Eğer yaklaşık bir MemorySegment 64 bayt uzunluğundadır, son 16 bayt şu şekilde kurtarılabilir:

Duyurudan sonra devamını okuyun


MemorySegment segment = arena.allocate(64);
MemorySegment info = segment.asSlice(48, 16);

segment buradaki tüm hafıza alanını içerir. Yöntem asSlice() 48. konumdan başlayarak 16 baytı keser.

Veriler kopyalanmaz ancak seçilen alanın görünümü oluşturulur. Alanın içeriği değişirse (örnekte alan info), orijinal hafıza alanı da değişir segment.

Segmentin uzunluğu yanlış olduğunda sorun ortaya çıkar reinterpret Segmentin uzunluğunu yeniden tanımlayabilirsiniz. Bazı durumlarda, yerel işlevin bir değer döndürmesi gibi, segment 0 uzunluğuyla döndürülebilir. void*-İşaretçi geri döner. Segmente erişim şunları içerecektir: ArrayIndexOutOfBoundException tetiklemek. Bu nedenle öncelikle segmenti bilindiğini varsayarak doğru uzunluğa ayarlamanız gerekir.

Aşağıdaki örnekte yerel işlev döndürülür getMemory() A void*-Bir hafıza alanına işaretçi. Ayrıca hafıza alanının 100 byte olduğu da bilinmektedir. Daha sonra sonuca aşağıdaki şekilde erişebilirsiniz:


MemorySegment segment = (MemorySegment) method.invoke();
MemorySegment value = segment.reinterpret(100);
String result = value.getString(0);
System.out.println("Result getMemory:" + result);

Belirli bir platformda bir veri türünün C'de kaç bayt kapladığı genellikle belirsizdir. Aşağıdaki kod, platformunuzdaki veri türünün boyutunu alır. Kod, desteklenen tüm veri türlerini belirler ve veri türü için gerekli boyutu bayt cinsinden ve hizalamayı gösterir. long İLE:


public void printTypeInfos()
{
  Map<String, MemoryLayout> typeInfos = linker.canonicalLayouts();
  System.out.println("Canonical layout keys: " + 
                     typeInfos.keySet());
  printTypeInfo(typeInfos, "long");
}

private void printTypeInfo(Map<String, MemoryLayout> typeInfos, 
                           String type)
{
  MemoryLayout typeLayout = typeInfos.get(type);
  if (typeLayout != null)
  {
    System.out.println("C '" + type + "' layout: " + typeLayout +  
                       ", size=" + typeLayout.byteSize() + ", 
                       align=" + typeLayout.byteAlignment());
  }
  else
  {
    System.out.println("Datentyp“ + type + " nicht in“  +  
    „canonicalLayouts() enthalten");
  }
}	

Bir kütüphaneyi birden fazla platformda, birden fazla işletim sisteminde kullanmak istiyorsanız, öncelikle bir işletim sistemiyle başlamalı ve başarılı bir şekilde kullandıktan sonra, kullanılan işlevlerin diğer işletim sistemlerinde de sorunsuz olarak kullanılıp kullanılamayacağını kontrol etmelisiniz.

Örneğin, Donanım Güvenlik Modülüne (HSM) erişim projemde, taşınabilirliğin bazen çok sorunlu olduğunu, çünkü mevcut sürücülere ve paylaşılan kitaplıklara bağlı olduğunu buldum. Bir HSM'ye bu şekilde erişildi opensc Linux altında sorun yok, Windows altında ise bazı işlevler hiç kullanılamıyor ve bunun yerine JVM'de erişim ihlaline neden oluyor.

Platformlar arasında önemli farklılıklar varsa bunun bir yolu, işlevselliği ortak bir temel sınıfa soyutlamak ve ardından onu her platform için türetilmiş sınıflarda farklı şekilde tasarlamaktır.

Platformlar arası erişimle ilgili olası sorunlar şunlardır:

  • farklı boyutlarda veri türleri,
  • farklı büyüklükteki yapılar,
  • Bir yapıdaki elemanların farklı hizalanması.

Bu durumda paylaşılan kütüphanenin kaynak koduna erişim faydalıdır. Eğer bu mümkün değilse bu yöntemle hedef platformdaki veri türlerinin boyutu hakkında kesin bilgi alabilirsiniz. printTypeInfos() belirlemek.

Yabancı İşlev ve Bellek API'si ile yaptığım çalışmalar sırasında bazı en iyi uygulamalar ortaya çıktı. Bu nedenle, daha büyük kitaplıklara veya daha karmaşık işlevlere geçmeden önce basit kitaplıklar ve işlevlerle başlamak mantıklıdır.

Mantıklı Throwable onları yakalamak ve bunları sizin tarafınızdan kullanılan istisnalarınıza dönüştürmek için RuntimeException bunlar türevlerdir.

Kitaplıktan aynı işlevlere birden çok kez ihtiyacınız varsa, aynı bilgiyi tekrar tekrar almak zorunda kalmamak için yöntem tanıtıcılarını bir haritada önbelleğe almalısınız. Yapılar her zaman adres aracılığıyla gönderilmelidir (ValueLayout.ADDRESS) ve yapıları ayrı bir sınıf olarak eşleyin, böylece kod açık kalır.

Uygulamaların bellek alanları ayırması gerekiyorsa, arena hiyerarşide mümkün olduğunca üstte olmalı ve uygulama arenayı içermelidir. try-with-resources Böylece hafıza otomatik olarak serbest bırakılabilir.

Jextract aracından kaçınmak en iyisidir. Manuel bir uygulamanın anlaşılması ve daha da önemlisi bakımı daha kolaydır.

Paylaşılan kütüphanenin kaynak kodu üzerinde kontrolü olan herkes aşağıdaki gibi veri türlerine dikkat etmelidir: int32_t VE int64_t Her birinin kaç bayt kapladığı net olacak şekilde ayarlayın.

Daha büyük projeler için, C işlevlerine erişim için bir temel katman uygulamak ve Java için erişim katmanını artık FFM'ye özgü ayrıntıları içermemesi gereken başka bir katmana eklemek uygun olabilir. Çok büyük projeler için en önemli kullanım durumlarını kapsayan ek bir konfor katmanı öneriyoruz.

Yabancı İşlev ve Bellek API'si ile Java ve C arasındaki etkileşimdeki hataların nedenlerini ararken bazı sorular yararlı olacaktır:

  • Doğru kütüphane yolu belirtildi mi?
  • Bu doğru paylaşılan kitaplık mı (32 bit mi yoksa 64 bit mi)?
  • Paylaşılan kütüphaneyi yüklemek mümkün mü?
  • İşlev adları doğru mu?
  • Parametreler (sayı ve veri türleri) ve dönüş değeri eşleşiyor mu?
  • Veri türleriyle ilgili olarak: boyutlar eşleşiyor mu? Özellikle veri türü long C'de bu önemlidir çünkü boyut platforma bağlı olarak değişir: Windows uygulamalarında onu şu şekilde kullanmak gerekir: JAVA_INT Linux altındaymış gibi davran ValueLayout.JAVA_LONG.


Yayımlandı

kategorisi

yazarı:

Etiketler:

Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir