Sử dụng JNI thay vì JNA để gọi mã gốc?


115

JNA có vẻ dễ sử dụng hơn một chút để gọi mã gốc so với JNI. Bạn sẽ sử dụng JNI thay vì JNA trong những trường hợp nào?


Một yếu tố quan trọng mà chúng tôi đã chọn JNA thay vì JNI, đó là JNA không yêu cầu bất kỳ sửa đổi nào đối với các thư viện gốc của chúng tôi. Việc phải tùy chỉnh các thư viện gốc chỉ để có thể làm việc với Java là một KHÔNG lớn đối với chúng tôi.
kvr

Câu trả lời:


126
  1. JNA không hỗ trợ ánh xạ các lớp c ++, vì vậy nếu bạn đang sử dụng thư viện c ++, bạn sẽ cần một trình bao bọc jni
  2. Nếu bạn cần sao chép nhiều bộ nhớ. Ví dụ, bạn gọi một phương thức trả về cho bạn một bộ đệm byte lớn, bạn thay đổi thứ gì đó trong đó, sau đó bạn cần gọi một phương thức khác sử dụng bộ đệm byte này. Điều này sẽ yêu cầu bạn sao chép bộ đệm này từ c sang java, sau đó sao chép lại từ java sang c. Trong trường hợp này jni sẽ thắng về hiệu suất vì bạn có thể giữ và sửa đổi vùng đệm này trong c mà không cần sao chép.

Đây là những vấn đề tôi gặp phải. Có thể có nhiều hơn nữa. Nhưng nhìn chung hiệu suất không khác biệt giữa jna và jni nên bạn dùng JNA ở đâu thì dùng.

BIÊN TẬP

Câu trả lời này có vẻ khá phổ biến. Vì vậy, đây là một số bổ sung:

  1. Nếu bạn cần ánh xạ C ++ hoặc COM, có một thư viện của Oliver Chafic, người tạo ra JNAerator, được gọi là BridJ . Nó vẫn còn là một thư viện trẻ, nhưng nó có nhiều tính năng thú vị:
    • Tương tác động C / C ++ / COM: gọi các phương thức C ++, tạo đối tượng C ++ (và lớp con C ++ từ Java!)
    • Ánh xạ kiểu đơn giản với việc sử dụng tốt các generic (bao gồm cả mô hình đẹp hơn nhiều cho Con trỏ)
    • Hỗ trợ đầy đủ JNAerator
    • hoạt động trên Windows, Linux, MacOS X, Solaris, Android
  2. Đối với việc sao chép bộ nhớ, tôi tin rằng JNA hỗ trợ ByteBuffers trực tiếp, vì vậy có thể tránh được việc sao chép bộ nhớ.

Vì vậy, tôi vẫn tin rằng bất cứ khi nào có thể, tốt hơn là sử dụng JNA hoặc BridJ và hoàn nguyên về jni nếu hiệu suất là quan trọng, bởi vì nếu bạn cần gọi các hàm gốc thường xuyên, thì hiệu suất sẽ đáng chú ý.


6
Tôi không đồng ý, JNA có nhiều chi phí. Mặc dù tiện lợi của nó là giá trị sử dụng trong phi mã thời gian quan trọng
Gregory Pakosz

3
Tôi khuyên bạn nên thận trọng trước khi sử dụng BridJ cho một dự án Android, trích dẫn trực tiếp từ trang tải xuống của nó : "BridJ hoạt động một phần trên trình giả lập Android / arm (với SDK) và có thể ngay cả trên các thiết bị thực tế (chưa được kiểm tra). "
kizzx2

1
@StockB: nếu bạn có một thư viện với api nơi bạn phải chuyển các đối tượng C ++ hoặc gọi các phương thức trên đối tượng C ++, JNA không thể làm điều đó. Nó chỉ có thể gọi các phương pháp vani C.
Denis Tulskiy

2
Theo như tôi hiểu, JNI chỉ có thể gọi các JNIEXPORThàm toàn cục . Tôi hiện đang khám phá JavaCpp như một tùy chọn, sử dụng JNI, nhưng tôi không nghĩ rằng JNI vani hỗ trợ điều này. Có cách nào để gọi các hàm thành viên C ++ bằng cách sử dụng vanilla JNI mà tôi đang bỏ qua không?
StockB


29

Thật khó để trả lời một câu hỏi chung chung như vậy. Tôi cho rằng sự khác biệt rõ ràng nhất là với JNI, việc chuyển đổi kiểu được thực hiện ở phía gốc của biên giới Java / native, trong khi với JNA, việc chuyển đổi kiểu được thực hiện trong Java. Nếu bạn đã cảm thấy khá thoải mái với việc lập trình bằng C và phải tự triển khai một số mã gốc, tôi sẽ cho rằng JNI có vẻ sẽ không quá phức tạp. Nếu bạn là một lập trình viên Java và chỉ cần gọi một thư viện gốc của bên thứ ba, sử dụng JNA có lẽ là cách dễ nhất để tránh những vấn đề có lẽ không quá rõ ràng với JNI.

Mặc dù tôi chưa bao giờ đánh giá bất kỳ sự khác biệt nào, nhưng tôi sẽ vì thiết kế, ít nhất giả sử rằng chuyển đổi kiểu với JNA trong một số tình huống sẽ hoạt động kém hơn với JNI. Ví dụ: khi truyền các mảng, JNA sẽ chuyển đổi các mảng này từ Java thành bản địa ở đầu mỗi lệnh gọi hàm và quay lại khi kết thúc lệnh gọi hàm. Với JNI, bạn có thể tự kiểm soát khi tạo "chế độ xem" gốc của mảng, có khả năng chỉ tạo chế độ xem của một phần của mảng, giữ chế độ xem trên một số lệnh gọi hàm và cuối cùng, hãy giải phóng chế độ xem và quyết định xem bạn có muốn để giữ các thay đổi (có thể yêu cầu sao chép lại dữ liệu) hoặc hủy các thay đổi (không cần sao chép). Tôi biết bạn có thể sử dụng một mảng gốc trên các lệnh gọi hàm với JNA bằng cách sử dụng lớp Bộ nhớ, nhưng điều này cũng sẽ yêu cầu sao chép bộ nhớ, có thể không cần thiết với JNI. Sự khác biệt có thể không liên quan, nhưng nếu mục tiêu ban đầu của bạn là tăng hiệu suất ứng dụng bằng cách triển khai các phần của nó trong mã gốc, thì việc sử dụng công nghệ cầu nối hoạt động kém hơn dường như không phải là lựa chọn rõ ràng nhất.


8
  1. Bạn đang viết mã một vài năm trước khi có JNA hoặc đang nhắm mục tiêu JRE trước 1.4.
  2. Mã bạn đang làm việc không có trong DLL \ SO.
  3. Bạn đang làm việc trên mã không tương thích với LGPL.

Đó chỉ là những gì tôi có thể nghĩ ra trên đỉnh đầu của mình, mặc dù tôi cũng không phải là người sử dụng nhiều. Cũng có vẻ như bạn có thể tránh JNA nếu bạn muốn có một giao diện tốt hơn giao diện mà họ cung cấp nhưng bạn có thể viết mã xung quanh đó trong java.


9
Tôi không đồng ý về 2 - chuyển đổi lib tĩnh thành lib động rất dễ dàng. Xem câu hỏi của tôi abput it stackoverflow.com/questions/845183/…
jb.

3
Bắt đầu với JNA 4.0, JNA là dual-cấp phép theo cả LGPL 2.1 và Apache License 2.0, và mà bạn chọn là tùy thuộc vào bạn
sbarber2

8

Nhân tiện, trong một dự án của chúng tôi, chúng tôi đã lưu giữ một bản in chân JNI rất nhỏ. Chúng tôi đã sử dụng bộ đệm giao thức để đại diện cho các đối tượng miền của chúng tôi và do đó chỉ có một hàm gốc để kết nối Java và C (tất nhiên sau đó hàm C đó sẽ gọi một loạt các hàm khác).


5
Vì vậy, thay vì một cuộc gọi phương thức, chúng tôi có thông báo đi qua. Tôi đã đầu tư khá nhiều thời gian cho JNI và JNA và cả BridJ, nhưng chẳng mấy chốc, tất cả đều trở nên quá đáng sợ.
Ustaman Sangat

5

Đó không phải là câu trả lời trực tiếp và tôi không có kinh nghiệm với JNA nhưng khi tôi xem các Dự án sử dụng JNA và thấy những cái tên như SVNKit, IntelliJ IDEA, NetBeans IDE, v.v., tôi có xu hướng tin rằng đó là một thư viện khá tốt.

Trên thực tế, tôi chắc chắn nghĩ rằng tôi sẽ sử dụng JNA thay vì JNI khi tôi phải làm vì nó thực sự trông đơn giản hơn JNI (có một quá trình phát triển nhàm chán). Thật tệ, JNA đã không được phát hành vào thời điểm này.


3

Nếu bạn muốn hiệu suất JNI nhưng lại nản lòng vì độ phức tạp của nó, bạn có thể cân nhắc sử dụng các công cụ tạo liên kết JNI tự động. Ví dụ: JANET (từ chối trách nhiệm: Tôi đã viết nó) cho phép bạn trộn mã Java và C ++ trong một tệp nguồn duy nhất, và ví dụ: thực hiện các cuộc gọi từ C ++ sang Java bằng cú pháp Java tiêu chuẩn. Ví dụ: đây là cách bạn in một chuỗi C ra đầu ra chuẩn Java:

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

Sau đó, JANET dịch Java nhúng backtick thành các lệnh gọi JNI thích hợp.


3

Tôi thực sự đã thực hiện một số điểm chuẩn đơn giản với JNI và JNA.

Như những người khác đã chỉ ra, JNA là để thuận tiện. Bạn không cần phải biên dịch hoặc viết mã gốc khi sử dụng JNA. Trình tải thư viện gốc của JNA cũng là một trong những trình tải tốt nhất / dễ sử dụng nhất mà tôi từng thấy. Đáng buồn thay, có vẻ như bạn không thể sử dụng nó cho JNI. (Đó là lý do tại sao tôi đã viết một giải pháp thay thế cho System.loadLibrary () sử dụng quy ước đường dẫn của JNA và hỗ trợ tải liền mạch từ classpath (tức là các chum).)

Tuy nhiên, hiệu suất của JNA có thể kém hơn nhiều so với JNI. Tôi đã thực hiện một thử nghiệm rất đơn giản gọi là hàm tăng số nguyên bản địa đơn giản "return arg + 1;". Các điểm chuẩn được thực hiện với jmh cho thấy rằng các lệnh gọi JNI tới hàm đó nhanh hơn 15 lần so với JNA.

Một ví dụ "phức tạp" hơn trong đó hàm gốc tổng hợp một mảng số nguyên gồm 4 giá trị vẫn cho thấy rằng hiệu suất JNI nhanh hơn 3 lần so với JNA. Lợi thế bị giảm đi có lẽ là do cách bạn truy cập các mảng trong JNI: ví dụ của tôi đã tạo một số thứ và phát hành lại nó trong mỗi thao tác tính tổng.

Bạn có thể tìm thấy mã và kết quả kiểm tra tại github .


2

Tôi đã điều tra JNI và JNA để so sánh hiệu suất bởi vì chúng tôi cần quyết định một trong số họ gọi một dll trong dự án và chúng tôi gặp phải hạn chế về thời gian thực. Kết quả cho thấy JNI có hiệu suất lớn hơn JNA (khoảng 40 lần). Có thể có một mẹo để có hiệu suất tốt hơn trong JNA nhưng nó rất chậm đối với một ví dụ đơn giản.


1

Trừ khi tôi thiếu thứ gì đó, không phải sự khác biệt chính giữa JNA và JNI là với JNA bạn không thể gọi mã Java từ mã gốc (C)?


6
Bạn có thể. Với một lớp gọi lại tương ứng với một con trỏ hàm ở phía C. void register_callback (void (*) (const int)); sẽ được ánh xạ tới public static native void register_callback (MyCallback arg1); trong đó MyCallback là một giao diện mở rộng com.sun.jna.Callback với một phương thức duy nhất void apply (int value);
Ustaman Sangat

@Augusto, đây cũng có thể là một bình luận
swiftBoy

0

Trong ứng dụng cụ thể của tôi, JNI tỏ ra dễ sử dụng hơn nhiều. Tôi cần đọc và ghi các luồng liên tục đến và từ một cổng nối tiếp - và không có gì khác. Thay vì cố gắng tìm hiểu cơ sở hạ tầng liên quan trong JNA, tôi thấy việc tạo mẫu giao diện gốc trong Windows dễ dàng hơn nhiều với một DLL có mục đích đặc biệt chỉ xuất ra sáu chức năng:

  1. DllMain (bắt buộc để giao diện với Windows)
  2. OnLoad (chỉ thực hiện một OutputDebugString để tôi có thể biết khi nào mã Java đính kèm)
  3. OnUnload (ditto)
  4. Mở (mở cổng, bắt đầu đọc và ghi luồng)
  5. QueueMessage (xếp hàng đợi dữ liệu xuất ra bởi luồng ghi)
  6. GetMessage (đợi và trả về dữ liệu nhận được bởi chuỗi đã đọc kể từ lần gọi cuối cùng)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.