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

website

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…