background
Recently, I was working on a project related to hardware and third-party platforms. The platform manufacturer threw me a DLL, but our platform is written in Java, so we need to realize the DLL that Java calls C. I have done some research and I will record it now.
Main implementation approaches
The script
In fact, the easiest way for Java to call other programs is directly through shell or BAT script calls, but this is only limited to some simple applications without interaction, I will not discuss here.
JNI
A simple introduction
JNI, which stands for Java Native Interface, ensures that code is portable across platforms by writing programs using the Java Native Interface. [1] Since Java1.1, the JNI standard has become part of the Java platform, allowing Java code to interact with code written in other languages. JNI was originally designed for native compiled languages, especially C and C++, but it doesn’t prevent you from using other programming languages, as long as the calling conventions are supported. Using Java to interact with natively compiled code often results in a loss of platform portability. However, there are situations where doing so is acceptable, even necessary. For example, using older libraries to interact with hardware, the operating system, or to improve program performance. At a minimum, the JNI standard ensures that native code can work in any Java virtual machine environment.
Using JNI often means an order of magnitude increase in complexity, which is inconvenient for future expansion and maintenance, but it may be necessary to use such a technology, most likely due to limited SDKS or extremely high performance requirements. If THE performance of PS C is an order of magnitude worse than that of Java, there are two main reasons. One is that there may not be sufficient warm-up and Java has not been JIT, and the other is that the code itself is written with problems.
The basic flow
Static registration
Principle: establish a one-to-one correspondence between Java methods and JNI functions according to function names. The process is as follows:
- Write Java native methods first;
- Then use the javah tool to generate the corresponding header file. Run the javah packagename. Classname command to generate the jni layer header file named by the packagename plus the classname. Or run the javah -o custom.h packagename. Classname command, where custom.h is the user-defined file name.
- Implement JNI functions, and then load the so library in Java via system. loadLibrary.
Dynamic registration
Principle: Native methods are directly told the pointer to their corresponding function in JNI. By using the JNINativeMethod structure to save the association between Java Native methods and JNI functions, the steps are as follows:
- Write Java native methods first;
- Write the implementation of JNI functions (function names can be arbitrarily named);
- The structure JNINativeMethod is used to preserve the correspondence between Java Native methods and JNI functions.
- RegisterNatives (JNIEnv* env) is used to register all local methods of the class;
- Call the registration method in the JNI_OnLoad method;
- After loading the JNI dynamic library through system. loadLibrary in Java, the JNI_OnLoad function is called to complete the dynamic registration.
JNA
JNA is the best known library for cross-language invocation in Java after JNI. JNA includes a small platform-specific shared library that supports all native access. Since the actual project uses JNA, let’s take a closer look at JNA.
The basic principle of
JNA includes a DLL or so library, and your JAVA code calls JNA’s JAR, which in turn calls its intermediate library, which in turn processes the real C/C++ library.
This is the same idea as JNI’s implementation, except that it is much simpler. Instead of compiling your Java into a header file and including it in the corresponding library, use an intermediate library to implement it.
Default type corresponds to
Half of the problems faced by JNA are typological, so it is important to understand typological relationships when developing projects
Native Type | Size | Java Type | Common Windows Types |
---|---|---|---|
char | 8-bit integer | byte | BYTE, TCHAR |
short | 16-bit integer | short | WORD |
wchar_t | 16/32-bit character | char | TCHAR |
int | 32-bit integer | int | DWORD |
int | boolean value | boolean | BOOL |
long | 32/64-bit integer | NativeLong | LONG |
long long | 64-bit integer | long | __int64 |
float | 32-bit FP | float | |
double | 64-bit FP | double | |
char* | C string | String | LPCSTR |
void* | pointer | Pointer | LPVOID, HANDLE, LPXXX |
There is a special problem that must be noticed.
- A char in C/C++ is equivalent to a byte in Java.
- A char in C/C++ is equivalent to a byte in Java.
- A char in C/C++ is equivalent to a byte in Java.
This is a particularly nasty problem. For Java, char is 0 to 255, and for C/C++ char is -127 to 128. So never use a Java char[] to join a C/C++ char array or pointer.
Code sample
My library file is in the project directory natives, named DLL. To isolate the utility classes, a DemoService middle layer is made. All internal services that call DemoService have nothing to do with JNA directly. A whole set of Dtos is also done to isolate coupling on types.
pom.xml
<! -- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.5.0</version>
</dependency>
Copy the code
interface
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("natives/dll", DemoLibrary .class);
int Do_Something(a);
}
Copy the code
In the middle of the Service
public class DemotService {
private static final String SDK_NAME = "dll";
private static volatile boolean initialized;
public TransportService(a) {
initialized = false;
}
@PostConstruct
public void init(a) {
DllUtil.loadNative(SDK_NAME);
initialized = true;
}
@PreDestroy
public void clear(a) {}public void do(a){
getInstance().Do_Something();
}
private DemoLibrary getInstance(a) {
returnDemoLibrar.INSTANCE; }}Copy the code
Utility class
package com.zw.ump.gateway4g.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
/** * Load DLL library utility class *@author zew
*/
@Slf4j
public class DllUtil {
public synchronized static void loadNative(String nativeName) {
String systemType = System.getProperty("os.name");
String fileExt = (systemType.toLowerCase().contains("win"))?".dll" : ".so";
String path = System.getProperty("user.dir")+ File.separator+"natives"+File.separator+nativeName+fileExt;
File sdkFile = new File(path);
System.load(sdkFile.getPath());
log.info("------>> Load SDK file :" + sdkFile + "Success!!!!!"); }}Copy the code
The efficiency problem
- Go straight to the native method
- Do not use non-mapped types, such as String, and go straight to native methods
- Java primitive arrays are generally slower to use than direct memory (Pointers, memory, or ByReference) or NIO buffers
- Large structures also have performance issues
- Finally the error throws the error
JNative
It’s basically similar to JNA.
The basic flow
- Download jnative. The jar and JNativeCpp. DLL
- Copy the USED DLL file and jnativecpp. DLL to system system32
- Write the code
public class JNativeTest {
// 1. Implement the demo. DLL file interface
public interface DemoLibrary extends Library {
// 2. HCTInitEx method in pegroute. DLL
public int do(int Version, String src);
}
public static void main(String[] args) {
//3. Load the DLL file and run the DLL method
DemoLibrary libary = (DemoLibrary) Native.loadLibrary("demo",
DemoLibrary.class);
if(libary! =null) {
System.out.println("DLL loaded successfully!");
int success = libary.do(0."");
System.out.println("1. Device initialization info!"+ success); }}}Copy the code
conclusion
- JNI has no additional middle layer and should be the most efficient. Of course I didn’t do much research and actual benchmarking, so I can only say that JNI should be a little more efficient than JNA.
- JNA comes with additional intermediate libraries, but since it does not require JAVA compilation and h import, it is easier to use and is most suitable for those who only know JAVA.
- JNI is used by many people, but it is relatively troublesome to be familiar with C and use javac and Javah commands. At the same time, JNI can do what JNA cannot achieve, that is, it can call Java content through C as well as JNI. JNA can only be called one-way.
- JNA is similar to JNative, but feels like JNA can be layered better. And JNative downloads a separate DLL. And JNative seems to have not been updated for a long time, not recommended.
reference
Blog.csdn.net/u011627980/… https://www.jianshu.com/p/ac00d59993aa baike.baidu.com/item/JNI/94…