Recently during the development of UMStor, it was necessary to write a Java SDK for C/C++ libraries. Considering that it would be too much work to completely rewrite a corresponding SDK in Java, I searched to see if it was possible for Java to access interfaces (.dll,.so) in C/C++ libraries. JNI

JNI(Java Native Interface) is a technology that can achieve the following:

– Functions in Java programs can call functions written in Native language. Native generally refers to functions written in C/C++. – Functions in Native programs can call Java layer functions, that is, functions that can call Java in C/C++ programs.

We all know that the virtual machine hosting the Java world is written in Native language, and the virtual machine runs on a specific platform, so the virtual machine itself cannot be platform independent. However, with JNI technology, the Java layer can be shielded from specific virtual machine implementation differences. In this way, you can implement the platform-independent features of Java itself. Java has always used JNI technology, but we don’t use it much.

The use of JNI is not simple, if there is a compiled. DLL /. So file, if the use of JNI technology call, we first need to use C language to write another. DLL /. Then load the library DLL/SO in Java, and finally write Java Native functions as the proxy of the functions in the linked library. You go through these tedious steps to call native code in Java.

JNA

JNA(Java Native Access) is a Java class library based on JNI technology, which enables us to use Java directly to Access functions in the dynamically linked library. Instead of rewriting our dynamic link library file, we have a direct call API, greatly simplifying our workload. However, JNA is generally only suitable for relatively simple C/C++ libraries, and is not recommended if the interface and data structure are complex. And JNA only provides C/C++ interface transformations to Java.

SWIG

SWIG(Simplified Wrapper and Interface Generator) is open source software designed to wrap C/C++ libraries into interfaces of other languages. Including :Java, Python, Perl, Ruby, C#, PHP and many other mainstream programming languages. SWIG is still JNI, if we want to access a simple C/C++ interface, then JNA is better. But if the interface is complex, SWIG is the best solution.

The C/C++ library interface I was dealing with was complex and used a lot of custom constructs. I should have used SWIG, but for some reason I dug a hole in myself by using JNA to develop the Java SDK.

This article will focus on how to pass complex data structures using JNA, so the introduction to JNA will not be covered here. You can search for it by the handful.

Comparison table of basic types

The following table is a comparison table between basic types in C and Java provided by JNA official website:


This table in general have been able to satisfy the cross-platform and cross-language call data type conversion needs, because if we do call across languages, should try to use the basic and simple data types, and try not to use too much complicated structure transmit data, because the structure of the C language in the body, each member will perform alignment operation maintain byte alignment with a member before, that is to say, The order in which the members are written affects how much space a structure takes up, which can cause a lot of trouble defining a structure on the Java side.

For example, if a function called across languages takes the stat structure as an argument: As we all know, the STAT structure is the basic structure used to describe file metadata in Linux file systems, but the trouble is that the order in which the members of the stat structure are defined varies from machine to machine, causing compatibility problems if we were to rewrite the structure on the Java side.

Structure definition

When we need to access a member of a C/C++ structure on the Java side, we need to override the structure on the Java side.

  • We need to define two inner classes ByReference and ByValue in the structure definition to implement the pointer type interface and the value type interface
  • Override getFieldOrder() to tell C/C++ members the order of values

Let’s take a look at how to simulate defining a C/C++ structure in Java:

C/C + + code

typedef struct A {
 B* b; 
 void* args;
 int len;
};

typedef struct B { 
 int obj_type;
};
Copy the code

Java code

Public static extends Structure {public A() {super(); } public A(Pointer _a) { super(_a); } // Struct member definition public b.byReference b; PointerByReference args; int len; // Add 2 inner classes, Public static class extends A implements Structure.ByReference {} public static class ByValue extends A implements structure. ByValue{} // Define an order of values that must be aligned with C/C++. NoSuchFieldError @override protected List getFieldOrder() {return array.asList (new String[]{"b", "args", "len"});  }} public static B extends Structure {public B() {super(); } public B(Pointer _b) { super(_b); } int obj_type; public static class ByReference extends B implements Structure.ByReference {} public static class ByValue extends B implements Structure.ByValue{} @Override protected List getFieldOrder() { return Arrays.asList(new String[]{"obj_type"}); }}Copy the code

Structural transfer

If you need to access a member of a structure from the Java side, use either ByReference or ByValue. Use PointerByReference and Pointer if you are just passing data and don’t care about the internal structure.

test_myFun(struct A a)

test_myFun(A.ByValue a)
Copy the code

test_myFun(struct A *a)

test_myFun(A.ByReference a)
Copy the code

test_myFun(struct A **a)

test_myFun(A.ByReference[] a)
Copy the code

test_myFun(A a)

test_myFun(Pointer a)
Copy the code

test_myFun(A *a)

test_myFun(PointerByReference a)
Copy the code

test_myFun(A **a)

test_myFun(PointerByReference[] a)
Copy the code

The callback function

We sometimes use C/C++ to call functions one by one, for example:

typedef bool (*test_cb)(const char *name);

int test_myFun(test_cb cb, const char *name, uint32_t flag);
Copy the code

If we need to call test_myFun on the Java side, we need to define the same callback interface as test\_cb on the Java side:

Public interface testCallback extends Callback {//invoke extends Callback (String name); }Copy the code

Define the implementation of the callback interface:

public class testCallbackImpl implements testCallback {
 @Override public int invoke(String name) { 
 System.out.printf("Invoke Callback " + name + " successfully!"); 
 return true; 
 } 
}
Copy the code

Test_myFun java-side definition:

int testMyFun(Callback cb, String name, int flag);
Copy the code

Call instance:

int rc = testMyFun(new testCallbackImpl(), "helloworld", 1);
Copy the code

conclusion

As you can see, the main difficulty with JNA is structure definition and delivery. Once you figure out how to deal with the structure, the rest will take care of itself. Having said so much, I just want to tell you that when making cross-language calls, we should try to encapsulate functions and structures to make data transfer easier.