JNA (Java Native Access) pattern collection

What is JNA

--One of the ways to call a shared library (so-called .dll or .so) written in C / C ++ from Java --Easier to use than conventional JNI (Java Native Interface) --No need to add C / C ++ code --Similar to Python's ctypes (personal opinion)

Java Native Access - Wikipedia Overview - JNA API Documentation

I wrote about Python ctypes here. Python: ctypes pattern collection

It's a hassle to think about new material, so the contents of the sample are exactly the same.

Preparation

Bring each .jar from the following sites. https://mvnrepository.com/artifact/net.java.dev.jna/jna https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform

Select the version and download "jar" in the Files column. Add the downloaded jar to your classpath.

If you are using Gradle, add two lines to the dependencies of build.gradle instead of downloading the jar yourself.

dependencies {
    // ...

    //Added the following two lines
    implementation 'net.java.dev.jna:jna:4.5.2'
    implementation 'net.java.dev.jna:jna-platform:4.5.2'

    // ...
}

Solution by pattern

Using the Windows API (Win32API) as a theme, we will summarize patterns in various cases.

Uninflected word

Sleep Function-MSDM

It's a simple program that only sleeps for 1 second. First from here.

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;

public class JNISample {
	public interface Kernel32 extends Library {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
	   	void Sleep(int dwMilliseconds);
	}

	public static void main(String[] args) {
		System.out.println("started");
		Kernel32.INSTANCE.Sleep(1000);
		System.out.println("finished");
	}
}

Pass a string

Let's display the message box using the MessageBox function.

MessageBox Function-MSDM

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

public class JNISample {
    public interface User32 extends Library {
        User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
        // A/Add A if there is a distinction between W
        int MessageBoxA(Pointer hWnd, String lpText, String lpCaption, int uType);
    }

    public static void main(String[] args) {
        User32.INSTANCE.MessageBoxA(null, "test", "title", 0);
    }
}

As we will see later, the window handle has a 64-bit value in a 64-bit OS, so it is an argument of the Pointer type.

If the source code is written in MS932 (Shift-JIS, CP932), this will work, but if the source code is written in UTF-8, the characters will be garbled. Therefore, use the Unicode version of the API and make the string argument WString type.

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;

public class JNISample {
	public interface User32 extends Library {
		User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
	   	// A/Add W if there is a distinction between W
	   	int MessageBoxW(Pointer hWnd, WString lpText, WString lpCaption, int uType);
	}

	public static void main(String[] args) {
		User32.INSTANCE.MessageBoxW(null, new WString("test"), new WString("title"), 0);
	}
}

This is OK. Set -encoding of javac appropriately. (If you are using Eclipse, you don't have to worry too much) Not limited to Windows API, if there is a function that receives wchar_t * type etc., let's pass it with WString.

From now on, we will handle the passing of character strings based on Unicode.

Receive data by reference

GetComputerName function --MSDN

  1. Get the required size of the buffer by passing by reference
  2. Allocate a buffer
  3. Get the result as a string
  4. View results

It is the flow of. Is the following points the points?

--Use Unicode version of function (W at the end of function name) --When you want to pass a ʻint`` type by reference, pass an object of the ʻIntByReferencetype. --You can get the value with getValue () --Similarly, there is also LongByReferenceetc. --String buffer argument should be of type char [] --In the case of an array, it will be passed by reference without thinking about anything. --If you pass null, you are passing a NULL pointer (void *) 0. --Convert the string buffer to Stringwith Native.toString () --It looks like it can be converted with new String () ``, but it is not interpreted as a null terminator and is followed by garbage.

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;

public class JNISample {
	public interface Kernel32 extends Library {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
	   	boolean GetComputerNameW(char[] lpBuffer, IntByReference lpnSize);
	}

	public static void main(String[] args) {
		IntByReference lenComputerName = new IntByReference();
		Kernel32.INSTANCE.GetComputerNameW(null, lenComputerName);
		char[] computerName = new char[lenComputerName.getValue()];
		Kernel32.INSTANCE.GetComputerNameW(computerName, lenComputerName);
		System.out.println(Native.toString(computerName));
	}
}

Execution result: Computer name


taro-pc

Arrays are passed by reference, so you can use an array instead of `ʻIntByReference``.

import com.sun.jna.Library;
import com.sun.jna.Native;

public class JNISample {
	public interface Kernel32 extends Library {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
	   	//lpnSize type int[]To
	   	boolean GetComputerNameW(char[] lpBuffer, int[] lpnSize);
	}

	public static void main(String[] args) {
	   	//Use an array of length 1
		int[] lenComputerName = new int[1];
		Kernel32.INSTANCE.GetComputerNameW(null, lenComputerName);
		char[] computerName = new char[lenComputerName[0]];
		Kernel32.INSTANCE.GetComputerNameW(computerName, lenComputerName);
		System.out.println(Native.toString(computerName));
	}
}

Structure

Example 1

GetCursorPos Function-MSDM

Define the structure in a class that inherits from com.sun.jna.Structure.

--Enumerate member variables as public --Implement getFieldOrder () to define the order in memory --Member variables must be listed in just proportion --this.getClass (). getFields () cannot be used because the order is undefined [^ 1]

If you specify the structure (class) as an argument, it will be automatically passed by reference.

JNISample.java


import java.util.Arrays;
import java.util.List;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;

public class JNISample {
	public interface User32 extends Library {
		User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
		boolean GetCursorPos(POINT lpPoint);
	}

	public static class POINT extends Structure {
		@Override
		protected List<String> getFieldOrder() {
			return Arrays.asList("X", "Y");
		}
		public int X;
		public int Y;
	}

	public static void main(String[] args) {
		POINT pt = new POINT();
		User32.INSTANCE.GetCursorPos(pt);
		System.out.println(String.format("x = %d, y = %d", pt.X, pt.Y));
	}
}

Execution result: Mouse cursor position


x = 340, y = 1061

Example 2

Next is the story when a fixed-length char array or another structure comes into the structure.

FindFirstFile function --MSDN FindNextFile function --MSDN FindCLose function --MSDN

--If you specify an array type as a structure member, it is equivalent to passing by value instead of a pointer. --Unlike arguments, it is not automatically passed by reference (pass by pointer). If you want to pass by reference, use Pointer type or ByteBuffer type. --The structure size is not determined until you assign an instance to all array type members. --In other words, there is no implicit initial value (you must assign an instance) --If you specify a structure as a structure member, it is equivalent to passing by value instead of a pointer. --Unlike arguments, it does not automatically pass by reference (pass by pointer) --The implicit initial value is all zero --Members with a size of 32bit on 32bit OS and 64bit on 64bit OS are defined by Pointer type. --Handle and pointer types (including `ʻUINT_PTRetc.) --Type of WPARAM/ LPARAM --If you want to see the actual value, you can get it with Pointer.nativeValue (ptr) ``

JNISample.java


import java.util.Arrays;
import java.util.List;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;

public class JNISample {
	public interface Kernel32 extends Library {
		Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
	   	Pointer FindFirstFileW(WString lpFileName, WIN32_FIND_DATAW lpFindFileData);
	   	boolean FindNextFileW(Pointer hFindFile, WIN32_FIND_DATAW lpFindFileData);
	   	boolean FindClose(Pointer hFindFile);
	}

	public static final int MAX_PATH = 260;
	public static final Pointer INVALID_HANDLE_VALUE = new Pointer(-1);

	public static class FILETIME extends Structure {
        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList("dwLowDateTime", "dwHighDateTime");
        }
		public int dwLowDateTime;
		public int dwHighDateTime;
	}

	public static class WIN32_FIND_DATAW extends Structure {
        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList(
            		"dwFileAttributes", "ftCreationTime", "ftLastAccessTime", "ftLastWriteTime",
            		"nFileSizeHigh", "nFileSizeLow", "dwReserved0", "dwReserved1",
            		"cFileName", "cAlternateFileName", "dwFileType", "dwCreatorType", "wFinderFlags"
            );
        }
		public int      dwFileAttributes;
		public FILETIME ftCreationTime;
		public FILETIME ftLastAccessTime;
		public FILETIME ftLastWriteTime;
		public int      nFileSizeHigh;
		public int      nFileSizeLow;
		public int      dwReserved0;
		public int      dwReserved1;
		public char[]   cFileName          = new char[MAX_PATH];
		public char[]   cAlternateFileName = new char[14];
		public int      dwFileType;
		public int      dwCreatorType;
		public short    wFinderFlags;
	}

	public static void main(String[] args) {
		String pattern = "C:\\Windows\\*.exe";
		WIN32_FIND_DATAW findData = new WIN32_FIND_DATAW();
		Pointer hfind = Kernel32.INSTANCE.FindFirstFileW(new WString(pattern), findData);
		if (hfind != INVALID_HANDLE_VALUE) {
			do {
				System.out.println(Native.toString(findData.cFileName));
			} while (Kernel32.INSTANCE.FindNextFileW(hfind, findData));
		}
		Kernel32.INSTANCE.FindClose(hfind);
	}
}

Execution result: Directly under the Windows folder.exe file list


bfsvc.exe
explorer.exe
HelpPane.exe
hh.exe
notepad.exe
regedit.exe
RtCRU64.exe
splwow64.exe
winhlp32.exe
write.exe

Example 3

GetOpenFileName function --MSDN

--Functions equivalent to sizeof in the Structure.size () method --Define the unchanged Unicode string with the WString type --Define a ByteBuffer type member when specifying the address of the memory area to be changed --It doesn't work well even if you specify an array type such as char [] (it is treated as passing by value as described above) --Reserve memory area with ByteBuffer.allocateDirect () --Memory contents can be copied to an array of any type --Be careful not to forget to specify ByteOrder

JNISample.java


import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.List;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;

public class JNISample {
	public interface Comdlg32 extends Library {
		Comdlg32 INSTANCE = (Comdlg32) Native.loadLibrary("comdlg32", Comdlg32.class);
		boolean GetOpenFileNameW(OPENFILENAME lpofn);
	}

	public static class OPENFILENAME extends Structure {
		@Override
		protected List<String> getFieldOrder() {
			return Arrays.asList(
					"lStructSize", "hwndOwner", "hInstance", "lpstrFilter", "lpstrCustomFilter", "nMaxCustFilter",
					"nFilterIndex", "lpstrFile", "nMaxFile", "lpstrFileTitle", "nMaxFileTitle", "lpstrInitialDir",
					"lpstrTitle", "Flags", "nFileOffset", "nFileExtension", "lpstrDefExt", "lCustData", "lpfnHook",
					"lpTemplateName", "pvReserved", "dwReserved", "FlagsEx"
			);
		}
		public int        lStructSize;
		public Pointer    hwndOwner;
		public Pointer    hInstance;
		public WString    lpstrFilter;
		public WString    lpstrCustomFilter;
		public int        nMaxCustFilter;
		public int        nFilterIndex;
		public ByteBuffer lpstrFile;
		public int        nMaxFile;
		public WString    lpstrFileTitle;
		public int        nMaxFileTitle;
		public WString    lpstrInitialDir;
		public WString    lpstrTitle;
		public int        Flags;
		public short      nFileOffset;
		public short      nFileExtension;
	  	public WString    lpstrDefExt;
	  	public Pointer    lCustData;
	  	public Pointer    lpfnHook;
	  	public WString    lpTemplateName;
	  	public Pointer    pvReserved;
	  	public int        dwReserved;
	  	public int        FlagsEx;
	}

	public static void main(String[] args) {
		OPENFILENAME ofn = new OPENFILENAME();
		final int lenFilenameBufferInChars = 1024;
		ByteBuffer buf = ByteBuffer.allocateDirect(lenFilenameBufferInChars * 2);
		ofn.lStructSize = ofn.size();
		ofn.lpstrFilter = new WString("text file\0*.txt\0\0");
		ofn.lpstrFile = buf;
		ofn.nMaxFile = lenFilenameBufferInChars;
		ofn.lpstrTitle = new WString("Please select a file");
		ofn.Flags = 0x00001000; // OFN_FILEMUSTEXIST
		boolean ret = Comdlg32.INSTANCE.GetOpenFileNameW(ofn);
		if (ret) {
			CharBuffer cbuf = buf.order(ByteOrder.LITTLE_ENDIAN).asCharBuffer();
			char[] arr = new char[cbuf.capacity()];
			cbuf.get(arr);
			System.out.println(Native.toString(arr));
		} else {
			System.out.println("was canceled");
		}
	}
}

Execution result: Selected file name


C:\Users\taro\test.txt

At this point, it becomes quite difficult. I struggled quite a bit with what to do with the types of struct members.

Callback function

Some Windows API functions call the specified callback function when an event occurs. For example, the `ʻEnumWindows`` function, which enumerates existing windows, notifies the found windows with a callback function.

EnumWindows Functions --MSDM

The flow is as follows.

--Define the interface of the callback function by inheriting the Callback interface --Define argument and return types with the ʻinvoke`` method --Implemented the ʻinvoke`` method of the interface defined earlier --Can also be implemented with anonymous functions

Since it is difficult to understand even if only the window handles are listed, an example of outputting with the window title is shown.

JNISample.java


import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

public class JNISample {
	public interface User32 extends Library {
		User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
		boolean EnumWindows(EnumWindowsProc lpEnumFunc, Pointer lParam);
		int GetWindowTextW(Pointer hWnd, char[] lpString, int nMaxCount);
	}

	public interface EnumWindowsProc extends Callback {
	    public boolean invoke(Pointer hWnd, Pointer lParam);
	}

	public static void main(String[] args) {
		User32.INSTANCE.EnumWindows(new EnumWindowsProc() {
			@Override
			public boolean invoke(Pointer hWnd, Pointer lParam) {
				char[] windowText = new char[1024];
				User32.INSTANCE.GetWindowTextW(hWnd, windowText, windowText.length);
				System.out.println(String.format("%x: %s", Pointer.nativeValue(hWnd), Native.toString(windowText)));
				return true;
			}
		}, null);
	}
}

Execution result: List of open windows (excerpt)


20730:Quick access
10226:Battery meter
9d09aa: eclipse
f05b4:calculator

Pointer pointer

I was wondering what to sample because I don't use it much in the Windows API, but here I will use the wvsprintf function, which is a string format function. In Java, you can do the same thing by using the String.format () function, so there is little need to execute it from Java.

wvsprintf function --MSDN

This function has a special way of passing arguments, but it is the same as passing a pointer to a string (that is, a pointer to a pointer in C language) as long as you pass only one string. (I will not talk about two or more here)

First, create an array of character strings and pass the array by reference.

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.WString;

public class JNISample {
	public interface User32 extends Library {
		User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
	   	int wvsprintfW(char[] lpOutput, WString lpFormat, WString[] arglist);
	}

	public static void main(String[] args) {
		char[] buf = new char[1024];
		String name = "Michael";
		WString[] argArray = new WString[] {new WString(name)};
		User32.INSTANCE.wvsprintfW(buf, new WString("My name is %s"), argArray);
		System.out.println(Native.toString(buf));
	}
}

Execution result: Formatted string


My name is Michael

Do the same thing differently. It looks a bit roundabout, but it's a more pointer-aware method.

JNISample.java


import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WTypes;
import com.sun.jna.ptr.PointerByReference;

public class JNISample {
	public interface User32 extends Library {
		User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
	   	int wvsprintfW(char[] lpOutput, WString lpFormat, Pointer arglist);
	}

	public static void main(String[] args) {
		char[] buf = new char[1024];
		String name = "Michael";
		WTypes.LPWSTR pname = new WTypes.LPWSTR(name); // wchar_t *
		PointerByReference argArray = new PointerByReference(pname.getPointer()); // wchar_t **
		User32.INSTANCE.wvsprintfW(buf, new WString("My name is %s"), argArray.getPointer());
		System.out.println(Native.toString(buf));
	}
}

The LPWSTR type that you see in the Windows API has come out, but it is actually a subclass of PointerType. Since ```IntByReferenceetc. also inherits PointerType, it can be treated in the same way as these ByReference`` series.

The argument of ```arglistis of type Pointer, but it can also be of type PointerByReference. In that case, you can pass `ʻargArray as is (without using getPointer () ) without conversion. In this way, you can write in various ways even when performing the same processing, but I think you should choose the method that is convenient for each time.

Summary

Perhaps we can do more, but I think it's enough to have a pattern like this as a starting point. You can look at other patterns when you want to do it (when you have to do it).

Reference page

Recommended Posts

JNA (Java Native Access) pattern collection
Run Rust from Java with JNA (Java Native Access)
☾ Java / Collection
Java9 collection
Access modifier [Java]
[Java] Strategy pattern
Java design pattern
java callback pattern
[Java] Singleton pattern
Java Reintroduction-Java Collection
[Java] Adapter pattern
[Java] Collection framework
Java pattern memo
Expired collection of java
Java collection interview questions
Access API.AI from Java
About Java access modifiers
Builder pattern (Effective Java)
Java Lambda Command Pattern
Java design pattern summary
Getting Started with Java Collection
Java parallelization code sample collection
[Design pattern] Java core library
[Java] Comparator of Collection class
Java test code method collection
What is a Java collection?
Enum Strategy pattern in Java
Deep copy collection in Java