--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.
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'
// ...
}
Using the Windows API (Win32API) as a theme, we will summarize patterns in various cases.
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");
}
}
Let's display the message box using the MessageBox function.
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.
GetComputerName function --MSDN
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));
}
}
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
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
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.
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.
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
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.
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.
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).
Recommended Posts