Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JNA - Let's C what it's worth

While coding in Java is (pretty) straight forward, some tasks require going beyond the borders of standard JVM coding practices in order to interface with low-level hardware & software programmatic APIs.

This slide will introduce the concept and basics of JNA, as well as discuss the probable caveats one might encounter when working with JNA, based on real production use-cases.

Related Books

Free with a 30 day trial from Scribd

See all

Related Audiobooks

Free with a 30 day trial from Scribd

See all

JNA - Let's C what it's worth

  1. 1. JNA - Let’s C what it’s worth Author: Idan Sheinberg
  2. 2. whoami Freelance software developer and all things open-source Hobbyist software developer and all things open-source Not much time for other stuff...
  3. 3. Let’s Start From The Begining JNA - Java Native Access - A means to access C/C++ code from the JVM. C/C++ (native) code is made accessible to the JNA by compiling it to a shared library. Yep, that means DLLs (Windows), SOs (Linux) or Dylibs(OSX). By using JNA, we are able to access native methods as if they were defined inside standard JVM jars.
  4. 4. Why Should I Even Do That? Be a Java programmer. Not (want to) be a C/C++ programmer Need a functionality/API provided only by native code. Need to interface with specific hardware only accessible through native code. Be a masochist/adventurous (maybe the same).
  5. 5. Don’t we already have JNI for that? JNI is the standard approach for interfacing with code written in C/C++ from the JVM. With JNI, the programmer must write C/C++ glue code to bridge the access to the actual native code you’re trying to interface with. That’s harder than your average Java related programming task, and also requires proficiency in at least the C. One might argue that a high-level language with a native interface should rid its practician from writing native code.
  6. 6. A few more JNI facts Lots of the low level mechanics in the JVM are implemented using JNI. ⏣ FileOutputStream, SocketInputStream, etc... JNI allows for full, bi-directional interaction with JVM heap objects. ⏣ It was designed for the purpose of serving as an extension/foundation to the JVM. ⏣ That’s probably the main cause for its sluggish/clumsy design. JNA, on the other hand, was created to solely ease the access to native code. ⏣ As there’s no(t supposed to be any) C/C++ code to write, there’s no point in dealing with bi-directional access.
  7. 7. How does JNA work? JNA uses libffi (Foriegn Function Interface Library) to invoke functions found in compiled C/C++ code from the JVM: ⏣ JNA uses a JNI (no kidding) library named libjnidispatch to glue libffi and handle type mapping & method proxying. ⏣ JNA (via libjnidispatch) also provides access to some useful low-level functions - malloc,memset,free, etc... ⏣ libffi supports many, many CPU architectures, operating systems and compilers, allowing JNA to function seamlessly regardless of the execution environment.
  8. 8. Working with JNA A tutorial for the eager beaver.
  9. 9. A quick start Given the following sample program: sample-lib.h: void printer(const char * input); sample-lib.c: #include <stdio.h> #include <sample-lib.h> void printer(const char * input) { printf("Input - %sn",input ); }
  10. 10. A quick start A matching JNA endpoint class might look like this: SampleLib.java: package org.sheinbergon.jna.sample; import com.sun.jna.Native; public class SampleLib { private final static String SAMPLE = "sample-lib"; static { Native.register(SAMPLE); } public static native void printer(String input); }
  11. 11. Working with structures Let’s define a C structure and a method to manipulate it: sample-structure-lib.h: typedef struct sample_structure { unsigned long int random; } SAMPLE_STRUCT; void random(const SAMPLE_STRUCTURE *); sample-structure-lib.c: #include <time.h> #include <stdlib.h> #include <sample-structure-lib.h> void random(const SAMPLE_STRUCT * strct) { srand(time(NULL)); strct->number = rand(); }
  12. 12. Working with structures First, let’s define a matching Structure class in Java: SampleStruct.java: package org.sheinbergon.jna.sample; import com.sun.jna.Structure; public class SampleStruct extends Structure { public int number ; @Override protected List<String> getFieldOrder() { return List.of("number"); } }
  13. 13. Working with structures Now, for the matching JNA endpoint class: SampleStructureLib.java: package org.sheinbergon.jna.sample; import com.sun.jna.Native; public class SampleStructureLib { private final static String SAMPLE_STRUCTURE = "sample-structure-lib"; static { Native.register(SAMPLE_STRUCTURE); } public static native void random(SampleStruct struct); }
  14. 14. Making DLLs available to the JVM. The JVM uses the OS library search path to begin with. ⏣ $LD_LIBRARY_PATH and ldconfig on Linux ⏣ %PATH% and Windows/System Directory on Windows. The local resource classpath should also work. ⏣ Be sure to place library file inside a directory matching the proper CPU architecture - linux-x86-64 for linux 64 bit, win32-x86-64 for windows 64 bit, etc. ⏣ File suffix/prefix is determined per OS - Loading a DLL named java-jna implies searching for a file named libjava-jna.so on Linux and java-jna.dll on Windows. And of course, there’s jna.library.path or java.library.path system properties
  15. 15. Type Mapping Working with pointers has never been more FUN!
  16. 16. Where’s that manual Standard JNA Type Mapping - https://github.com/java-native-access/jna/blob/master/www/Mappings.md. Unfortunately, the list above is lacking big time. ⏣ C/C++ coding conventions encourage passing pointers/references to maintain an efficient and flexible stack. ⏣ As opposed to JAVA, native function declaration does not differentiate between pointers to allocated memory or garbage. ⏣ Following such rules & guidelines with JNA is a bit trickier, and a lot more confusing. For the very least, it’s going to take some getting used to.
  17. 17. Pointer JNA provides us with the Pointer class. As its name implies, it proxies a native pointer. ⏣ Remember, A pointer does not necessarily refer to allocated memory! Common use cases for mapping types to a Pointer: ⏣ Pointers to types you don’t care about. ⏣ void * method/function argument/return type. ⏣ Instantiating a Structure sub-class.
  18. 18. Pointer Native code allows you to read any type of data at any offset using a pointer. A JNA Pointer provide the same functionality: ⏣ Let’s say we have int * cp = ... ⏣ For Pointer jp , calling jp.getInt(0L) is the same as *cp (the int value itself). ⏣ jp.getInt(4L) is the same *(cp+1). Pointer offset in native code matches sizeof(type) You can also copy entire arrays from native memory. ⏣ jp.getIntArray(0L,5) copies 20 bytes (5 * int32) from the location pointed by p to the JVM heap. ⏣ Useful for reading structure fields populated inside the native code.
  19. 19. ByReference char * is automatically translated to(and from) String. But what about other types, like int * for example ? ⏣ Are we dealing with a reference to pre-allocated or unallocated memory ? are we referencing an array or a single value? For single value references, JNA provides us with smart(ish) types like IntByReference ⏣ From the Java developer’s point-of-view, they are convenient containers that allow us to pass pointers to a JNA mapped method, and read the value set in a clearer way. What about void ** ? ⏣ PointerByReference is your friend.
  20. 20. Memory A sub class of Pointer, representing allocated native memory ⏣ That’s memory allocated on the native heap, of course. ⏣ Allocation takes place on instance construction (i.e. new Memory(20L)). Memory is one of the most useful classes when working with JNA: ⏣ Use whenever you need to pre-allocate a buffer to be populated by the innards of native method. ⏣ That’s a pretty common use-case.
  21. 21. Quite simple, ain’t it?
  22. 22. CAVEATS Things are never that simple...
  23. 23. Structures limitations Bit-Fields are not supported. ⏣ Aggregate all relevant bit fields onto the nearest type and parse possible bit values using bitwise operators. C: struct bitfields { unsigned short f1 : 7; unsigned short f2 : 9; }; JAVA: public class BitFields extends Structure { public short fshort; } Multi-Dimensional arrays are not supported. ⏣ Uni-dimensional arrays are just fine, so just be sure to encompass all of the dimensions size specifications - a field of type char buffer[3][4][5] in a C struct should be mapped as byte[] buffer = byte[60] in a matching JAVA Structure sub-class. Be accurate with your type selection/inclusion - Core dumps are no fun!
  24. 24. Passing allocated memory Let’s say a native void function expects an allocated byte buffer (char buffer[size]). ⏣ Function declaration would be in the form of void func(char * buffer). One might guess that the matching JNA setup should look like this: ⏣ Define a native method static native void func(ByteByReference buffer). ⏣ Create a buffer byte [] buffer = new byte[size], put it inside a pointer new ByteByReference(byte[0]) and pass it on to the method.
  25. 25. Passing allocated memory - Doing it Right JVM heap allocated arrays are NOT the same as native allocated memory: ⏣ Define a native method static native void func(Pointer buffer). ⏣ Create a Memory(size) buffer instance and pass it on. Memory is a sub-class of Pointer class, so no problem there. Structure sub-classes array fields are an exception, and are considered valid. ⏣ The JNA layer takes care of copying from/to native code and memory allocation C: struct yarrr { byte by[20]; }; JAVA: public class Yarrr extends Structure { public byte [] by = new byte[20]; }
  26. 26. memory allocation Consider the following native code sample: // Struct definition typedef struct { char * f1; int f2; } STRUCT; // Some function void func(STRUCT * S){...} // Main code loop for(int i=0;; i++) { STRUCT s = { .f1 = "text", .f2 = i }; ... func(s); }
  27. 27. memory allocation Matching JNA mapping might look like this: // Struct defintion public class Struct extends Structure { public static Struct of(String f1,int f2){Struct s = new Struct();s.f1=f1;s.f2=f2;return s;} public String f1; public int f2; ... } // JNA function mapping static native void func(Struct s); // Main code loop for(int i=0;; i++) { Struct s = Struct.of("text",i); ... func(s); }
  28. 28. memory allocation - pressure In C, a local variable’s memory is allocated in the stack on declaration and gets deallocated once it goes out of scope: ⏣ Out of scope means end of block - loop iteration, function end, etc. ⏣ Local means every variable inside the bounds of a block aside from memory explicitly allocated. In the JVM, class instances are created inside the heap and would eventually get GC’d once they are no longer referenced. Eventually being the keyword - it’s not always fast-enough… ⏣ This behavior could lead to memory exhaustion, native memory contention, sluggishness, and ugly crashes.
  29. 29. Memory allocation - pressure - Solution In order to simulate native C stack memory allocation, we could simply reuse structure instances instead of re-allocating them in each loop iteration. ⏣ Please don’t forget to “clear” the structure before reusing (“cheaper” than additional memory allocation). // Main code loop Struct s = new Struct(); for(int i=0;; int++) { s.clear(); s1.f1 = “text”; s1.f2 = i; ... func(s); }
  30. 30. Weak-References Weak references refer to object instances that might be GC’d during contention, even though the instances themselves might still be in-use/referenced. ⏣ Say we have a method void callee(SomeObject obj){...} ⏣ Calling it with callee(new SomeObject()) will create a weak-reference to that SomeObject instance. Doing the same with JNA mapped structures are no different, of course. The consequences, however, are far more impactful: ⏣ Structure GC also frees native memory. That memory might still be currently accessed by native code. ⏣ In fact, sometimes structures allocated are not meant to be freed as part of the ongoing flow.
  31. 31. Weak-References - Solution Make sure everything passed down through JNA proxied methods is strongly referenced. ⏣ Static fields or members of singletons/objects the only go out of scope as part of lifecycle management ⏣ This applies to Structure , Pointer , Callback , *ByReference subclasses/instances.
  32. 32. Memory Pressure - Structure Synchronization When passing Structure sub-classes through JNA mapped methods, Object fields defined in the JVM need to be written to native memory and made available to the native code. ⏣ The same goes for fields being read after a JNA method call. The problem starts when you’re dealing with nested structure definition (consider structure array fields, re-instantiating on each read). This could cause heap memory exhaustion and slow down you’re overall application performance. ⏣ A Full GC might also be triggered, causing weak-reference (and other) issues to arise earlier.
  33. 33. Memory Pressure - Structure Synchronization - Solution Luckily for us, we can choose what gets synced and what does not: ⏣ We can disable or enable read/write synchronization separately or both altogether. public class Sync extends Strucutre {public int f1;...} Sync s = new Sync(); s.setAutoWrite(false); // Disable only pre JNA method write sync s.setAutoSynch(true); // Enable both read and write sync; ⏣ For maximum efficiency, we can also synchronize specific fields manually. s.readField("f1"); // Post JNA method call Only read/write fields that are required to properly fulfill the code’s functionality.
  34. 34. Memory Corruption - Pragma pack Native code compilers might pad structure fields, so they’ll match the default byte alignment of the target CPU architecture. ⏣ Given a structure that’s supposed to require 5 bytes: struct structure { char f1; // 1 bytes in32_t f2; // 4 bytes }; ⏣ Memory allocated for the structure might be “rounded” up to 8 bytes: struct structure { char f1; // 1 bytes /* 3 bytes padding */ in32_t f2; // 4 bytes };
  35. 35. Memory Corruption - Pragma pack That’s cool, butttttttttttttttttt ⏣ JNA needs to allocate and set memory in the same manner the compiled code behaves. ⏣ Mismatching in this setting might and probably will cause structure fields to be written beyond the bounds of its allocated memory. ⏣ This would result in vary nesty heap-corruptions, the kind that’ll send you searching the Glibc’s source code.
  36. 36. Memory Corruption - Pragma pack - Solution JNA Provides us with the means of setting the alignment type. ⏣ You can set it on a per Structure basis : public class SampleStruct extends Structure { public SampleStruct(){ setAlignType(ALIGN_NONE); } } ⏣ Or globally (on library load): public class SampleLib { static { Native.register(NativeLibrary.getInstance(SAMPLE, Map.of(Library.OPTION_STRUCTURE_ALIGNMENT,Structure.ALIGN_NONE))); } }
  37. 37. Getting proffesionall help JNA Github repository is actively maintained ⏣ https://github.com/java-native-access/jna ⏣ Browsing through the readme, you can see it’s in active use by several major projects The google group is also alive & kicking ⏣ https://groups.google.com/forum/#!forum/jna-users ⏣ A good place to search for answers or just rant and let some steam out.
  38. 38. THE BOTTOM LINE Decisions Decisions
  39. 39. Is JNA a good choice? AND
  40. 40. Pros JNA is a mature & stable solution. ⏣ After all, JNA is a just proxy/bridge between Java & Native code. ⏣ If you’re doing everything right, no special treatment/race-condition handling is required. Fully supported on Linux, OSX and Windows ⏣ That includes the nuisance of dealin with Windows __stdcall convention and special types. When keeping your eco-system pure JVM is of high importance, JNA is probably your go-to solution. ⏣ It really narrows down the list of things you can’t easily achieve with a JVM programming language
  41. 41. Cons JNA requires the developer to be precise and accurate ⏣ C/C++ programming has the same requirement. ⏣ Java, as high level programming language, has gotten us used to getting by with a much lower degree of accuracy. JNA requires the developer for the very least to be able to read and understand C/C++ code. ⏣ You need to understand the pointer types populated/passed on and their scope. ⏣ And what about thread-safety ? Documentation provided with native code is sometimes lacking. ⏣ Going JNA without the source code and/or a native reference implementation might be considred suicide.
  42. 42. My 2 cents Build upon JNA when no other standard pure-JVM solution seem viable for a requirement. ⏣ The lack of existence of, or unmaintained/poor-quality code. Don’t doubt JNA - doubt your own implementation instead. ⏣ JNA is harder until you get the hang of it - there’s no hiding from that truth. RTFM, seriously ⏣ https://github.com/java-native-access/jna/blob/master/www/FrequentlyAskedQuestions.md ⏣ http://java-native-access.github.io/jna/4.2.1/overview-summary.html#overview.description
  43. 43. Use Case #1 Needed to convert live raw (wav) audio feed to AAC-encoded segments. ⏣ Segments need to be sequentually concatable. ⏣ The program need to to know epoch start timestamp of each segment (real world clock). ⏣ The JVM can read audio and determine timestamp of read data at any given point, but cannot encode raw audio to AAC. ⏣ FFmpeg can segment audio-only stream, but cannot imprint epoch timestamps.
  44. 44. Use Case #1 Solution - Created a bridge between libfdk-aac and the JVM using JNA ⏣ Project repository https://github.com/sheinbergon/jna-aac-encoder ⏣ Raw (wav) audio is read from a capture interface in real-time using the Java (old but working) AudioSystem. ⏣ Each x bytes read are encoded to AAC (I’m passing byte buffers back and forth). ⏣ Timetstamp is recorded prior to reading the next chunk of bytes.
  45. 45. Use Case #2 Needed to find a microsecond-fast way to send messages from a C program to a Java(Kotlin) program, on Windows. ⏣ Socket I/O or Memory mapping was too “low-level” in C. ⏣ Brokerless MQs seemed like a viable solution. ⏣ ZeroMQ/Czmq are very standard solutions, but windows builds are a complete mess. ⏣ JeroMQ might have been considered a good pure JVM alternative, but it wasn’t fast enough. ⏣ Nanomsg is a viable alternative (faster than ZeroMQ - 150-400 us per message), but the Java client requires MSVC and ANT...Ya’ak
  46. 46. Use Case #2 Solution - Load the Nanomsg DLL from the JVM via JNA ⏣ Nanomsg DLL compiled using CLion/MinGW under Windows within 5 minutes. ⏣ Just took the DLL and made it loadable to the JVM application ⏣ Followed the samples here http://nanomsg.org/gettingstarted/pipeline.html and mapped minimal required methods for consumer code ⏣ Implemented a consumer server thread in Java with JNA. ⏣ This took around 2.5 hours to wind up with a fully working sample.
  47. 47. Questions Common, you know you want to
  48. 48. Thank You!

    Be the first to comment

    Login to see the comments

  • yangchsh

    Jul. 6, 2018
  • JiejingZhang

    Dec. 12, 2019
  • KaushiKannan

    Apr. 18, 2020
  • kdkanishka

    Jun. 29, 2020
  • JungwookSong

    Jul. 13, 2020

While coding in Java is (pretty) straight forward, some tasks require going beyond the borders of standard JVM coding practices in order to interface with low-level hardware & software programmatic APIs. This slide will introduce the concept and basics of JNA, as well as discuss the probable caveats one might encounter when working with JNA, based on real production use-cases.

Views

Total views

2,661

On Slideshare

0

From embeds

0

Number of embeds

61

Actions

Downloads

24

Shares

0

Comments

0

Likes

5

×