Java Native Access and Eclipse Indigo Swing GUI editor
by
, July 6th, 2011 at 12:59 AM (31530 Views)
Normally for native access, I would use JNI to run native code, especially code I've written which interfaces with Java components. However, often you just want to call native functions (particularly system calls).
Rather than having to go through the whole JNI process (which is rather painstakingly slow), someone has created the JNA library which allows you to basically call native methods directly.
As far as I understand, there are two main ways to use JNA:
1. Create an interface which extends the JNA Library class, then load the appropriate native library with that interface template.
2. Declare native methods in your class. Then register that class with a native library. This is also known as "Native Mapping".
Both methods have their uses, at this moment I'm not entirely sure what the difference between the two are other than semantics. I have read a few sites (particularly the old Java.net site for the JNA project) saying that native mapping has some performance benefits over using an interface, but I haven't investigate if this is true or not.
So for fun, I decided to both play with the new Window Designer in Eclipse Indigo and JNA.
Getting setup
All you need to get started with JNA is the JNA jar file. It contains all the necessary native libraries packed in it to run on virtually any platform.
The main JNA website: JNA on GitHub
Note: There is a java.net website which is supposedly the home of JNA, that is the old home (a shame since it's the first item which comes up on a google search). It will probably re-direct you to the new home on GitHub.
Here's a direct link to the JNA.jar file: JNA.jar download link
The second file is the Javadoc for JNA: doc.zip download link
To make life easier for me, I created an Eclipse User Library (see JavaTip Dec 18, 2010: Eclipse User Libraries for how to do this).
So now I have a project with the JNA user library added to the build path.
First thing, I create a new Record class. This class runs on a separate thread and constantly polls if the state of different keys are being changed. Note: There is code found in this class which is Windows-Specific. Honestly, I don't know what the equivalent compatible code in Linux or Mac OS would be. However, if someone figures it out, I would like to know.
package record; import javax.swing.JTextArea; import com.sun.jna.Native; public class Record implements Runnable { static { Native.register("User32"); } public static native short GetKeyState(int KeyState); private boolean[] keyPresses; private long delay; private boolean keepRunning; private boolean paused; private JTextArea textArea; public Record(JTextArea textArea, long delay) { this.delay = delay; keyPresses = new boolean[256]; keepRunning = true; this.textArea = textArea; paused = false; } public void setDelay(long delay) { this.delay = delay; } public long getDelay() { return delay; } public void signalStop() { textArea.append("stopped\r\n"); keepRunning = false; } public void signalPause() { textArea.append("pause\r\n"); paused = true; } public void signalResume() { paused = false; textArea.append("resume\r\n"); synchronized (this) { notify(); } } @Override public void run() { try { long startTime = System.currentTimeMillis(); long currentTime; while (keepRunning) { if (paused) { synchronized (this) { wait(); } } Thread.sleep(delay); for (int i = 0; i < keyPresses.length; ++i) { boolean key = (Record.GetKeyState(i) & 0x8000) != 0; currentTime = System.currentTimeMillis(); if (key != keyPresses[i]) { textArea.append(currentTime - startTime + "\t" + i + "\t" + key + "\r\n"); keyPresses[i] = key; } } } } catch (InterruptedException e) { } } }
For those are use to JNI, you'll immediately notice some differences, particularly with loading libraries.
static { Native.register("User32"); }
Native is a class of the JNA library which allows you to directly map a class's native methods to the respective native library. This is similar to System.LoadLibrary(), but here you'll notice that I'm directly linking the Windows library rather than creating a "middle-man" library (in this case, User32.dll).
There is another way to load native libraries with JNA, and that's using interfaces.
import com.sun.jna.Library; public interface User32Lib extends Library { short GetKeyState(int KeyState); }
Now, to actually load the native library:
User32Lib userLibInstance = (User32Lib) Native.loadLibrary("User32", User32Lib.class);
Typically, you'd want to load this as a static field of the User32Lib interface so it's easy to find where the opened instance can be found at.
import com.sun.jna.Library; public interface User32Lib extends Library { public static final User32Lib INSTANCE = (User32Lib) Native.loadLibrary("User32", User32Lib.class); // technically public and static are redundant, but I put them here just to be verbose short GetKeyState(int KeyState); }
The GUI
So now that I have the logic setup, it's time for the GUI.
The GUI is a simple JFrame with a JTextArea and 3 JButtons. The JTextArea is surrounded by a JScrollPane.
First things first: Click on "new->other...", then go down to the "WindowBuilder->Swing Designer" folder and select "JFrame".
Click next, and type in the name for your JFrame class. I called mine RecordActions, and placed it in the "gui" package. I also left the "Use Advanced Template for generate JFrame" checked.
First thing you may notice when the file gets created is that there are two little tabs at the bottom, one for "source" and the other for "design". Shown below is the design tab.
For the most part, you can add items to the GUI in one of two ways:
1. Click on the component you want to add.
2a. Hover over a part of the "mock-up" GUI and the GUI will highlight where the item will be added in conformance with the current LayoutManager of the component. --OR--
2b. Go over to the "Components" screen on the top left side. You can add it as a child of a component by clicking on it, or as a sibling by clicking between components.
If you select a component (either in the Mock-up GUI or in the tree display), the properties tab will populate with properties that you can edit.
If the property you're looking for isn't there, you can also turn on "Show Advance Properties" by right-clicking on properties tab and enabling that option.
The Window Builder also allows you to easily add event listeners. These are added in the form of Anonymous classes.
To add an action listener to the buttons, right-click on one, then go to "Add Event Handler"->"action"->"actionPerformed".
That should take you over to the source tab, where you can edit the event handler code.
Here's the finished GUI code:
package gui; import java.awt.BorderLayout; public class RecordActions extends JFrame implements WindowListener { /** * */ private static final long serialVersionUID = -2123765083016662420L; private JPanel contentPane; private Record record; private JTextArea textArea; private JButton btnResume; private JButton btnPause; private JButton btnStop; /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { RecordActions frame = new RecordActions(); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the frame. */ public RecordActions() { addWindowListener(this); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setBounds(100, 100, 450, 300); contentPane = new JPanel(); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); contentPane.setLayout(new BorderLayout(0, 0)); setContentPane(contentPane); textArea = new JTextArea(); textArea.setEditable(false); contentPane.add(new JScrollPane(textArea), BorderLayout.CENTER); record = new Record(textArea, 50); Thread t = new Thread(record); t.start(); JPanel panel = new JPanel(); contentPane.add(panel, BorderLayout.SOUTH); panel.setLayout(new GridLayout(1, 0, 0, 0)); btnResume = new JButton("Resume"); btnResume.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { record.signalResume(); } }); panel.add(btnResume); btnPause = new JButton("Pause"); btnPause.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { record.signalPause(); } }); panel.add(btnPause); btnStop = new JButton("Stop"); btnStop.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { record.signalStop(); } }); panel.add(btnStop); } @Override public void windowActivated(WindowEvent e) { } @Override public void windowClosed(WindowEvent e) { record.signalStop(); } @Override public void windowClosing(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowIconified(WindowEvent e) { } @Override public void windowOpened(WindowEvent e) { } public JTextArea getTextArea() { return textArea; } }
For the most part, you can edit the bare Java code safely even when the Window Builder is open. For example, in my code above I manually added that the RecordActions class implements the WindowListener interface. I also added code which interfaces the logic found in the Record class with my RecordActions GUI.
Anyways, I hope you found this blog useful. I'm looking forward to building more GUI's with the new designer, as well as playing more with JNA.
Happy Coding