ใช้ JNI แทน JNA เพื่อเรียกรหัสเนทีฟ?


115

JNA ดูเหมือนจะใช้งานง่ายกว่าเล็กน้อยในการเรียกรหัสเนทีฟเมื่อเทียบกับ JNI คุณจะใช้ JNI กับ JNA ในกรณีใดบ้าง?


ปัจจัยสำคัญอย่างหนึ่งที่เราเลือก JNA มากกว่า JNI คือ JNA ไม่ต้องการการแก้ไขใด ๆ กับไลบรารีดั้งเดิมของเรา การต้องปรับแต่งไลบรารีเนทีฟเพื่อให้สามารถทำงานกับ Java ได้นั้นไม่ใช่เรื่องใหญ่สำหรับเรา
kvr

คำตอบ:


126
  1. JNA ไม่รองรับการแมปคลาส c ++ ดังนั้นหากคุณใช้ไลบรารี c ++ คุณจะต้องใช้กระดาษห่อ jni
  2. หากคุณต้องการการคัดลอกหน่วยความจำจำนวนมาก ตัวอย่างเช่นคุณเรียกวิธีการหนึ่งที่ส่งคืนบัฟเฟอร์ไบต์ขนาดใหญ่คุณเปลี่ยนแปลงบางอย่างในนั้นจากนั้นคุณต้องเรียกใช้วิธีอื่นที่ใช้บัฟเฟอร์ไบต์นี้ สิ่งนี้ต้องการให้คุณคัดลอกบัฟเฟอร์นี้จาก c เป็น java จากนั้นคัดลอกกลับจาก java เป็น c ในกรณีนี้ jni จะชนะในประสิทธิภาพเนื่องจากคุณสามารถเก็บและแก้ไขบัฟเฟอร์นี้ใน c โดยไม่ต้องคัดลอก

นี่คือปัญหาที่ฉันพบ อาจจะมีมากกว่านี้ แต่โดยทั่วไปประสิทธิภาพไม่ได้แตกต่างกันระหว่าง jna และ jni ดังนั้นทุกที่ที่คุณสามารถใช้ JNA ให้ใช้

แก้ไข

คำตอบนี้ดูเหมือนจะเป็นที่นิยมมากทีเดียว ดังนั้นนี่คือส่วนเพิ่มเติมบางส่วน:

  1. หากคุณจำเป็นต้อง map C ++ หรือ COM มีห้องสมุดโดยโอลิเวอร์ Chafic ผู้สร้าง JNAerator เรียกBridJ ยังคงเป็นห้องสมุดเล็ก แต่มีคุณสมบัติที่น่าสนใจมากมาย:
    • Dynamic C / C ++ / COM interop: เรียกใช้เมธอด C ++ สร้างวัตถุ C ++ (และคลาสย่อย C ++ จาก Java!)
    • การแมปประเภทที่ตรงไปตรงมาพร้อมการใช้ generics ที่ดี (รวมถึงรูปแบบที่ดีกว่ามากสำหรับ Pointers)
    • รองรับ JNAerator เต็มรูปแบบ
    • ทำงานบน Windows, Linux, MacOS X, Solaris, Android
  2. สำหรับการคัดลอกหน่วยความจำฉันเชื่อว่า JNA รองรับ ByteBuffers โดยตรงดังนั้นจึงสามารถหลีกเลี่ยงการคัดลอกหน่วยความจำได้

ดังนั้นฉันยังคงเชื่อว่าเมื่อใดก็ตามที่เป็นไปได้ควรใช้ JNA หรือ BridJ ดีกว่าและเปลี่ยนกลับเป็น jni หากประสิทธิภาพเป็นสิ่งสำคัญเพราะหากคุณต้องการเรียกใช้ฟังก์ชันเนทีฟบ่อยๆประสิทธิภาพจะสังเกตได้ชัดเจน


6
ฉันไม่เห็นด้วย JNA มีค่าใช้จ่ายมาก แม้ว่าความสะดวกสบายจะคุ้มค่ากับการใช้รหัสที่ไม่ใช่เวลาที่สำคัญ
Gregory Pakosz

3
ขอแนะนำให้ใช้ความระมัดระวังก่อนใช้ BridJ สำหรับโปรเจ็กต์ Android เพื่ออ้างอิงโดยตรงจากหน้าดาวน์โหลด : "BridJ ทำงานบางส่วนบนตัวเลียนแบบ Android / arm (กับ SDK) และอาจใช้กับอุปกรณ์จริง (ยังไม่ทดสอบ) "
kizzx2

1
@StockB: หากคุณมีไลบรารีที่มี api ซึ่งคุณต้องส่งผ่านวัตถุ C ++ หรือเรียกวิธีการบนวัตถุ C ++ JNA จะไม่สามารถทำได้ สามารถเรียกวิธีวานิลลา C เท่านั้น
Denis Tulskiy

2
เท่าที่ฉันเข้าใจ JNI สามารถเรียกJNIEXPORTใช้ฟังก์ชันส่วนกลางเท่านั้น ฉันกำลังสำรวจ JavaCpp เป็นตัวเลือกซึ่งใช้ JNI แต่ฉันไม่คิดว่า vanilla JNI รองรับสิ่งนี้ มีวิธีเรียกใช้ฟังก์ชันสมาชิก C ++ โดยใช้ vanilla JNI ที่ฉันมองเห็นหรือไม่?
StockB


29

เป็นเรื่องยากที่จะตอบคำถามทั่วไปเช่นนี้ ฉันคิดว่าความแตกต่างที่ชัดเจนที่สุดคือเมื่อใช้ JNI การแปลงประเภทจะถูกนำไปใช้ที่ด้านเนทีฟของเส้นขอบ Java / เนทีฟในขณะที่ JNA การแปลงประเภทจะดำเนินการใน Java หากคุณรู้สึกสบายใจกับการเขียนโปรแกรมในภาษา C และต้องติดตั้งโค้ดเนทีฟด้วยตัวเองฉันคิดว่า JNI จะไม่ซับซ้อนเกินไป หากคุณเป็นโปรแกรมเมอร์ Java และจำเป็นต้องเรียกใช้ไลบรารีเนทีฟของบุคคลที่สามเท่านั้นการใช้ JNA น่าจะเป็นเส้นทางที่ง่ายที่สุดในการหลีกเลี่ยงปัญหาที่อาจไม่ชัดเจนกับ JNI

แม้ว่าฉันจะไม่เคยเปรียบเทียบความแตกต่างใด ๆ แต่ฉันก็เป็นเพราะการออกแบบอย่างน้อยก็สมมติว่าการแปลงประเภทด้วย JNA ในบางสถานการณ์จะทำได้แย่กว่ากับ JNI ตัวอย่างเช่นเมื่อส่งผ่านอาร์เรย์ JNA จะแปลงสิ่งเหล่านี้จาก Java เป็นเนทีฟที่จุดเริ่มต้นของการเรียกใช้ฟังก์ชันแต่ละครั้งและย้อนกลับเมื่อสิ้นสุดการเรียกใช้ฟังก์ชัน ด้วย JNI คุณสามารถควบคุมตัวเองได้เมื่อสร้าง "มุมมอง" ดั้งเดิมของอาร์เรย์ซึ่งอาจเป็นเพียงการสร้างมุมมองของส่วนหนึ่งของอาร์เรย์เก็บมุมมองไว้ในการเรียกใช้ฟังก์ชันต่างๆและในตอนท้ายปล่อยมุมมองและตัดสินใจว่าคุณต้องการหรือไม่ เพื่อเก็บการเปลี่ยนแปลง (อาจต้องคัดลอกข้อมูลกลับ) หรือทิ้งการเปลี่ยนแปลง (ไม่จำเป็นต้องทำสำเนา) ฉันรู้ว่าคุณสามารถใช้อาร์เรย์เนทีฟในการเรียกฟังก์ชันด้วย JNA โดยใช้คลาสหน่วยความจำ แต่สิ่งนี้จะต้องมีการคัดลอกหน่วยความจำด้วย ซึ่งอาจไม่จำเป็นกับ JNI ความแตกต่างอาจไม่เกี่ยวข้องกัน แต่หากเป้าหมายเดิมของคุณคือการเพิ่มประสิทธิภาพของแอปพลิเคชันโดยการนำบางส่วนมาใช้ในโค้ดเนทีฟการใช้เทคโนโลยีบริดจ์ที่มีประสิทธิภาพแย่กว่าดูเหมือนจะไม่ใช่ทางเลือกที่ชัดเจนที่สุด


8
  1. คุณกำลังเขียนโค้ดเมื่อสองสามปีก่อนก่อนที่จะมี JNA หรือกำลังกำหนดเป้าหมายก่อน 1.4 JRE
  2. รหัสที่คุณใช้งานไม่ได้อยู่ใน DLL \ SO
  3. คุณกำลังทำงานกับรหัสที่เข้ากันไม่ได้กับ LGPL

นั่นเป็นเพียงสิ่งเดียวที่ฉันสามารถคิดขึ้นมาได้แม้ว่าฉันจะไม่ใช่คนใช้งานหนักก็ตาม ดูเหมือนว่าคุณอาจหลีกเลี่ยง JNA หากคุณต้องการอินเทอร์เฟซที่ดีกว่าที่มีให้ แต่คุณสามารถเขียนโค้ดใน java ได้


9
ฉันไม่เห็นด้วยเกี่ยวกับ 2 - การแปลง static lib เป็น dynamic lib นั้นง่ายมาก ดูคำถามของฉันโดยย่อstackoverflow.com/questions/845183/…
jb.

3
เริ่มต้นด้วย JNA 4.0 JNA เป็นคู่ที่ได้รับอนุญาตภายใต้ทั้ง LGPL 2.1 และ Apache License 2.0 และสิ่งที่คุณเลือกนั้นขึ้นอยู่กับคุณ
sbarber2

8

อย่างไรก็ตามในหนึ่งในโครงการของเราเราเก็บภาพพิมพ์ JNI ขนาดเล็กมาก เราใช้โปรโตคอลบัฟเฟอร์เพื่อแสดงอ็อบเจ็กต์โดเมนของเราดังนั้นจึงมีฟังก์ชันเนทีฟเพียงฟังก์ชันเดียวในการเชื่อมโยง Java และ C (แน่นอนว่าฟังก์ชัน C จะเรียกฟังก์ชันอื่น ๆ อีกมากมาย)


5
ดังนั้นแทนที่จะเรียกวิธีการเรามีข้อความส่งผ่าน ฉันลงทุนไปพอสมควรใน JNI และ JNA และ BridJ แต่ในไม่ช้าพวกเขาก็น่ากลัวเกินไป
Ustaman Sangat

5

มันไม่ใช่คำตอบโดยตรงและฉันไม่มีประสบการณ์กับ JNA แต่เมื่อฉันดูโครงการที่ใช้ JNAและเห็นชื่อเช่น SVNKit, IntelliJ IDEA, NetBeans IDE ฯลฯ ฉันมักจะเชื่อว่ามันเป็นห้องสมุดที่ดีทีเดียว

จริงๆแล้วฉันคิดว่าฉันจะใช้ JNA แทน JNI เมื่อฉันต้องใช้มันดูง่ายกว่า JNI (ซึ่งมีกระบวนการพัฒนาที่น่าเบื่อ) เสียดายที่ตอนนี้ JNA ไม่ได้เปิดตัว


3

หากคุณต้องการประสิทธิภาพของ JNI แต่กังวลกับความซับซ้อนคุณอาจพิจารณาใช้เครื่องมือที่สร้างการเชื่อม JNI โดยอัตโนมัติ ตัวอย่างเช่นJANET (ข้อจำกัดความรับผิดชอบ: ฉันเขียนไว้) ช่วยให้คุณสามารถผสมโค้ด Java และ C ++ ในซอร์สไฟล์เดียวและเช่นโทรจาก C ++ ไปยัง Java โดยใช้ไวยากรณ์ Java มาตรฐาน ตัวอย่างเช่นต่อไปนี้เป็นวิธีพิมพ์สตริง C ไปยังเอาต์พุตมาตรฐาน Java:

native "C++" void printHello() {
    const char* helloWorld = "Hello, World!";
    `System.out.println(#$(helloWorld));`
}

จากนั้น JANET จะแปล Java ที่ฝังแบ็กทิกเป็นการเรียก JNI ที่เหมาะสม


3

จริงๆแล้วฉันทำเกณฑ์มาตรฐานง่ายๆกับ JNI และ JNA

ดังที่คนอื่น ๆ ชี้ให้เห็นแล้ว JNA มีไว้เพื่อความสะดวก คุณไม่จำเป็นต้องคอมไพล์หรือเขียนโค้ดเนทีฟเมื่อใช้ JNA ตัวโหลดไลบรารีดั้งเดิมของ JNA ยังเป็นหนึ่งในตัวโหลดที่ดีที่สุด / ง่ายที่สุดที่ฉันเคยเห็น น่าเศร้าที่คุณไม่สามารถใช้กับ JNI ได้ (นั่นเป็นเหตุผลที่ฉันเขียนทางเลือกสำหรับ System.loadLibrary ()ที่ใช้รูปแบบเส้นทางของ JNA และรองรับการโหลดอย่างราบรื่นจาก classpath (เช่น jars))

อย่างไรก็ตามประสิทธิภาพของ JNA อาจแย่กว่า JNI มาก ฉันได้ทำการทดสอบอย่างง่าย ๆ ซึ่งเรียกว่าฟังก์ชันการเพิ่มจำนวนเต็มเนทีฟอย่างง่าย "return arg + 1;" Benchmarks ที่ทำด้วย jmh แสดงให้เห็นว่า JNI เรียกใช้ฟังก์ชันนั้นเร็วกว่า JNA 15 เท่า

ตัวอย่างที่ "ซับซ้อน" มากขึ้นซึ่งฟังก์ชันเนทีฟจะรวมอาร์เรย์จำนวนเต็ม 4 ค่ายังคงแสดงให้เห็นว่าประสิทธิภาพของ JNI เร็วกว่า JNA 3 เท่า ข้อได้เปรียบที่ลดลงอาจเป็นเพราะคุณเข้าถึงอาร์เรย์ใน JNI ได้อย่างไร: ตัวอย่างของฉันได้สร้างบางสิ่งและปล่อยออกมาอีกครั้งในระหว่างการดำเนินการสรุปแต่ละครั้ง

รหัสและผลการทดสอบสามารถพบได้ที่ GitHub


2

ฉันตรวจสอบ JNI และ JNA เพื่อเปรียบเทียบประสิทธิภาพเพราะเราจำเป็นต้องตัดสินใจหนึ่งในนั้นเพื่อเรียก dll ในโครงการและเรามีข้อ จำกัด ด้านเวลาจริง ผลการวิจัยพบว่า JNI มีประสิทธิภาพมากกว่า JNA (ประมาณ 40 เท่า) อาจมีเคล็ดลับสำหรับประสิทธิภาพที่ดีขึ้นใน JNA แต่มันช้ามากสำหรับตัวอย่างง่ายๆ


1

เว้นแต่ฉันจะขาดอะไรไปความแตกต่างหลักระหว่าง JNA กับ JNI กับ JNA คุณไม่สามารถเรียกโค้ด Java จากโค้ดเนทีฟ (C) ได้หรือไม่?


6
คุณสามารถ. ด้วยคลาสการเรียกกลับที่สอดคล้องกับตัวชี้ฟังก์ชันที่ด้าน C โมฆะ register_callback (โมฆะ (*) (const int)); จะถูกแมปกับโมฆะเนทีฟแบบคงที่สาธารณะ register_callback (MyCallback arg1); โดยที่ MyCallback เป็นอินเทอร์เฟซที่ขยาย com.sun.jna.Callback ด้วยวิธีเดียวใช้โมฆะ (ค่า int);
Ustaman Sangat

@ สิงหาคมนี้สามารถแสดงความคิดเห็นได้เช่นกัน
swiftBoy

0

ในแอปพลิเคชันเฉพาะของฉัน JNI พิสูจน์แล้วว่าใช้งานง่ายกว่ามาก ฉันจำเป็นต้องอ่านและเขียนสตรีมแบบต่อเนื่องไปยังและจากพอร์ตอนุกรม - และไม่มีอะไรอื่น แทนที่จะพยายามเรียนรู้โครงสร้างพื้นฐานที่เกี่ยวข้องใน JNA ฉันพบว่าการสร้างต้นแบบอินเทอร์เฟซเนทีฟใน Windows นั้นง่ายกว่ามากด้วย DLL วัตถุประสงค์พิเศษที่ส่งออกเพียงหกฟังก์ชัน:

  1. DllMain (จำเป็นสำหรับการเชื่อมต่อกับ Windows)
  2. OnLoad (ทำแค่ OutputDebugString ดังนั้นฉันจึงสามารถรู้ได้เมื่อแนบโค้ด Java)
  3. OnUnload (ditto)
  4. เปิด (เปิดพอร์ตเริ่มอ่านและเขียนเธรด)
  5. QueueMessage (ข้อมูลคิวสำหรับเอาต์พุตโดยการเขียนเธรด)
  6. GetMessage (รอและส่งคืนข้อมูลที่ได้รับจากเธรดการอ่านตั้งแต่การโทรครั้งสุดท้าย)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.