
Using the Java Native Interface with Delphi
(Part One - Introduction and Tutorial)
You interface Java with Delphi using JNI. The JNI allows you to call in both directions: Java to Delphi or Delphi to Java. So you can extend your Delphi applications with Java classes. For implementing what you want on Android, doing it in Java will be the easier path to follow, as some available examples do exactly what you have in mind. Cool, I'm writing a service in java to access the serial port and communicate. Delphi XE5 components (libnodave, JEDI, Serial port) Edit.
Part One - Introduction and Tutorial
Part Two - Techniques and Examples
Part Three - The Invocation API
Contents
| Quick Start | If you're familiar with Delphi and JNI and want to get started right away. |
| Background | Why JNI? Why Delphi? |
| Overview | What's here? |
| Translation | A brief look at translating the C/C++ header file to Delphi. |
| Example | The canonical HelloWorld program. |
| A Closer Look | The HelloWorld program in excruciating detail. |
| Troubleshooting | Typical errors when programming with the JNI. |
| Debugging | Stepping through your Delphi DLL at runtime. |
| Summary | What's next? |
| Helpful Resources | Sources to help you with Delphi and the JNI |
C/C++ programmers should check out these pages:Experienced JNI/Delphi programmers should grab JNI.pas and JNI_MD.INC and get started. The source files usedin this part of the tutorial can be downloaded here.Programming with the Java Native Interface This is a tutorial that shows how to use theJava Native Interface with Linux, Solaris, and Windows NT.
Using the Java Native Interface with C++ This has a lot of examples of how you mightuse the Java Native Interface. Contrasts Sun's vision with Microsoft's. Also includes somecrude benchmarks. This is an older document. (January 1998)
Clone software protection dongle. Delphi programmers may also want to check out the C/C++ links above. Although those pages are targeted at C/C++ programmers, there isinformation about the Java Native Interface (not necessarily related to C/C++) that may not be duplicated here.
A couple of years ago I did someexploration of the Java Native Interface. At that time, everything 'native' was being done in C/C++.That really wasn't a problem, though, since the syntax of the Java language targets C++ programmers. (This is clearly evident by the similarities between the two languages.) Now, this isn't to say that Java is identicalto C++ (or unlike other languages for that matter.) It's just an observation of the syntax of the two languages. Also, all of the available JNI documentation/references were aimed at C/C++ programmers.My first go-round with the JNI is documented here. I basically just presented my findings (albeit, crude.) There wasn't much documentation around at the time (circa January 1998), so I didn't have too many references. After that initial exposure, I refined the experiment. That result was more of a tutorial and can be found here.
Since the time of those experiments, I have continued to receive occasional emails from people asking for further assistance. When I was ableto help, I did. Until recently, I hadn't even looked at the JNI stuff. That changed a few weeks ago when I decided to try implementing thenative portion using Borland/Inprise's Delphi language. The result: The acronyms 'JNI' and 'RAD' can now be used in the same sentence. I'm not goingto get into a debate over why one would want to use native code from within Java code, I'm just going to show you how to do it.Again, this document is aimed specifically at Delphi programmers. C/C++ programmers would want to check out one or both of the links mentioned above.
Before starting this, you should be able to compile and execute Java code on your machine. This document focuses on getting you up to speed with Delphi and the Java Native Interface. I'm not an expert on Java, and setting up and configuring a compile-time and run-time environment sometimes seems harder than actually writing the Java code (although I think that the Java 2 stuff makes it a little easier.)Another important point about this Delphi interface is that it hasn't been tested thoroughly. I present several different 'tests' (examples), butthere are over 200 distinct functions in the API of the JNI. I'm probably only using a couple dozen at most. But, you'll see how similar they are. Infact, 90% of the Delphi interface looks pretty repetitive. Once you get the 'pattern' down, you can easily translate any function's signature. For Part One, though, you shouldn't have any problems because I'm really not using any of the JNI API.
One last thing: I used version 5.0 of Delphi to develop this. I'm not sure how the other versions will handle it. If I find the time, I will run thetests through the other versions. (I have all 5 versions of Delphi, I just don't want to spend the time installing/uninstalling them!!) If anyonetries this with another version, please feel free to let me know how it went.
The tutorials have been updated to reflect Kylix, the Linux version of Delphi. These documents were originally written before Kylix, so naturallythey were Windows-only. However, most (if not all) of the material applies to Kylix and Linux as well. Rather than re-write a separate document for Kylix(which would be about 98% identical in content) or use the phrases 'Delphi or Kylix' and 'DLL or SO' everywhere, I've left it Windows-specific.I've tried to point out any differences between the two environments, but may have missed something along the way. When I say Delphi, I am referring to both Delphi and Kylix. When I say DLL, I am referring to both DLLs (.DLL) on Windows and shared libraries (.so) on Linux.
First off, it's important to note that this isn't the only possible translation. As I will show later, you can translate a type incorrectly (loosely speaking), but as long as the two types have the same size, everything will work. As a preview to this, you'll see a lot of things typed as Pointer, which is very generic, and as far as the JNI is concerned, any type that is exactly 4-bytes wide (at the time of this writing) is compatible with a Delphi pointer.
Originally, I had planned to explain the jni.pas in detail. But, I've decided to leave that for a more advanced topic. It's notthat the translation is advanced (quite the contrary), it's just that it isn't necessary to understand the details to start using the JNI with Delphi, whichI'm assuming is why you're here. I will describe a couple of key points about the translation from C/C++ to Delphi. If you are not interested (and it'squite alright if you are not interested) you can skip to the Example below. If you do wish to know more, you should look atthese:
- jni.pas - A Delphi interface to the Java Native Interface.
- The JNI Specification - This is Sun's documentation. It's well writtenand is considered the authority on the subject. If there is a conflict between what I say and what Sun says, you should follow Sun's advice.
The two most important guidelines when translating a C/C++ header file (interface) is to make sure that:
- the Delphi type you are mapping to a C/C++ type is the same size as the C/C++ type.
- the fields declared in a Delphi record are in the same order as the fields in the corresponding C/C++ struct.
It is important that the data types that are passed between Javaand native code are the same size. The table below shows this mapping. The types labeledas Native Type are the types that will be defined in Delphi. This is safer thantrying to remember that a long in Java is an Int64 in Delphi, or that an int in Javais a Longint in Delphi. Also, there is no void type in Delphi, but there is a 'void pointer',Pointer, which we will see a lot. This table is not exhaustive. It just shows the primitivetypes.
| Java Type | Delphi Type | C++ Type | Native Type | Description |
| boolean | Boolean | unsigned char | jboolean | 8 bits, unsigned |
| byte | Shortint | char | jbyte | 8 bits, signed |
| char | WideChar | unsigned short | jchar | 16 bits, unsigned |
| double | Double | double | jdouble | 64 bits |
| float | Single | float | jfloat | 32 bits |
| int | Longint | long | jint | 32 bits, signed |
| long | Int64 | __int64 | jlong | 64 bits, signed |
| short | Smallint | short | jshort | 16 bits, signed |
| void | N/A | void | void | N/A |
The type definitions below are excerpts from jni_md.pas and jni.paswhich show how Delphi types are mapped into Java types:
Preview:
Here are a couple of Java functions:and this is how they would appear in Delphi:Actually, the functions would look more like this:but we're not there yet. At this point, it's important that you recognize the correlation between Java typesand Delphi types.Order
The majority of the methods declared in jni.pas are methods of a this record:Every translation of the C/C++ header file jni.hmust declare the methods of a recordin the exact same order.
Note that the order of the records in the Delphi file is not important. Only the order of the methods within the records is important.
One of the best ways to learn a new programming skill is by example. Inkeeping with tradition, I present the canonical programming example thatsimply prints the words 'Hello World!' to the display. The twist is thatJava code will invoke a native function to do the actual printing via the Delphi library function, WriteLn.1. Create the Java files. First, create the two Java files as shown below.
HelloWorld.java:Main.java:2. Compile the Java files. 3. Create and build the Delphi file. You will end up with a file called HelloWorldImpl.dll.
HelloWorldImpl.dpr:4. Execute the program. You should open a command window (DOS prompt) in the directory where your Delphi and Javafiles are and execute it there.1. Create the Java FilesHelloWorld.java Create the Java class that declares the native method. In this example, theJava class definition is trivial. There are no members or methods defined within the class. There are 2 key points here:
- Make sure to include the native keyword when declaring the native method, otherwise, it's just another Java class method. This also tells theJVM at runtime that the function is implemented in a DLL somewhere and not inthis class.
- Also, be sure that the name of the shared library is correct. Note that thename of the DLL does not have to have the same name as the class that exposes themethod. In the example, the name of the class is HelloWorld and thename of the DLL is HelloWorldImpl. (The static section of a class is executed when theclass is loaded. We use this section to load the DLL that contains the implementation of the native method.)
There is now a tool available that will produce a Delphi/Kylix project file (.dpr) from a Java class file (.class). It is available from my Delphi/JNI home page. There are actually two tools: a Java-based one (JavaToDPR) and a native Delphi-based one (javadpr) that do the same thing.
Main.java Create the Java class that will test the native method.
- Notice that this Java code has no knowledge that the method displayHelloWorld is a native method. This allows the implementorof HelloWorld.displayHelloWorld() to implement it either as a native method, or as a Java method. The calling program will neverknow and will never need to be changed if the implementation of HelloWorld.displayHelloWorld() ever changes.
This, of course, compiles the Java files and creates two files, HelloWorld.class and Main.class, respectively.3. Create and build the Delphi file.Now things are getting a little interesting. There are several points here that you should take notice of. I'll go through this code inmuch more detail than the Java code above. That's the main purpose of this document: I want to show you how to use Delphi to implementnative methods to be called from Java. I've also included line numbers below, so that I can easily refer to the source code.
First of all, notice that the file that implements the native method, HelloWorldImpl.dpr,is a .dpr file: a Delphi project file. Some Delphi programmers may never have spent much time dealing with this file. Thisis quite natural, since the Delphi IDE does a pretty good job of generating and modifying this file for you automatically. The projectfile is not that different from other units (.pas files.) I decided to put everything in the project file because the code toimplement the method was so trivial. (You could have put the function in a unit file and then added the unit to the uses sectionin the project file.)
Ok, let's look at the file in detail:
- Line 1: Since this is a DLL, and not an executable program (.exe), we use the library keyword to tell Delphi to create a DLL file. The name of the DLL will be HelloWorldImpl.dll.
- Line 4: This is probably the most important line (if there can be only one.) This file, jni.pas,contains the interface to the JNI. This will be used by every DLL that needs to be accessed by Java. You can basically consider ita black box. However, you have the source, so you can view it to get a better understanding of what's going on behind-the-scenes.
- Line 6: There are several pieces of important information here that must be specified with each function thatis going to be called from Java:
- This function is actually a Delphi procedure. This is simply because there is no return value. Had the Java classdeclared the method to return a value, you would have seen the function keyword instead. We will see functions later on. Unlessotherwise specified, when I refer to a Delphi function I am also talking about a Delphi procedure as well.
- All functions/procedures that you want to be accessed from a Java class must have the term Java_ prepended to them. Also,the name of the class that declares the native method (HelloWorld in our example) must follow the Java_ term. An underscore character follows the Java class name and, finally, the name of the method itself. In our example, the fully decorated function name is Java_HelloWorld_displayHelloWorld. Unlike Delphi, the Java language is case-sensitive,so you must make sure that the Delphi function definitions use the same case as the Java method declarations. (Another reason for a toollike javah. Maybe call it javad? Any takers?)
- What gives with the parameters? There were no parameters specified in the Java class! Every Java method that is implemented in nativecode will have two additional parameters. They are both pointers that the native function can use to access functions in the Java object that calledthe function and to access functions in the Java runtime environment. The types PJNIEnv and JObject are both defined in jni.pas
The first parameter is a pointer to the runtime environment. The second parameteris a pointer to the object that called this method. The second parameter is sort of like the Self member of a class in Delphi. In thisexample, since the function is so trivial, we don't use either of these. However, it is important that you include them in the definition of thefunction because they will always be passed in to the function whether or not they are used. Later, we will see heavy use of these parameters.
- Unless you have written DLLs before or have had to access code written in another language (or implemented a Windows callback), you may have neverused the stdcall directive. This tells Delphi to generate code that will push the arguments onto the stack from right to left. Delphiuses the fast register calling convention by default, which uses a combination of registers and the stack to pass arguments to functions.It's not important to understand the details, but if you forget to add thestdcall directive, your DLL will most surely cause an exception.
Kylix note: Use the cdecl directive instead of stdcall. If you plan on writing apps to run on both Windows andLinux, you should use the IFDEF technique shown above.
- Line 8: This is what we are actually trying to accomplish! It seems like a lot of work to print a line of text, and it is. However,it's doubtful that anyone would go through this much trouble to print a line of text when they could clearly do it from Java. But, as youprobably have already figured out, in the real world we would be using/implementing some serious native code that just isn't possible or efficient to implement in Java.
- Line 12: In order for our function to be accessible from the outside, we must export it. This is done inthe exports section of a Delphi library (DLL.) Notice that you don't provide the parameters or function/procedure keyword.
- Java loads Main.class and calls main. This is typical for Java program.
- Java executes the line:This line causes a new HelloWorld object to be created. When this class is created, the code inside it's staticsection executes:The System.loadLibrary method causes the DLL (HelloWorldImpl.dll) to be loaded by the operating system. If our DLL would havewanted to perform any kind of startup action or initialization, it would have performed it at this time. (For this simple example there is noinitialization code.)
- Next, Java executes the line:This is a call to the displayHelloWorld method in the HelloWorld object. However, since this method is a native methodof the HelloWorld object, the call ends up going to the displayHelloWorld method (Java_HelloWorld_displayHelloWorld)implemented in HelloWorldImpl.dll. This displays the text Hello World! on the screen. This 'routing' of method calls from a Java object to a native-code DLL is all transparent to the calling class, Main.Graphically, the process looks like this:
View a large image here.
These are the explanations of the circled numbers in the graphic:
- Java loads Main.class and calls the public static method main.
- Java creates a new HelloWorld object.
- When the HelloWorld object is created, it implicitly calls System.loadLibrary('HelloWorldImpl'); (because it is in thestatic section of the class) which loads the DLL into memory.
- The DLL is loaded into memory by the operating system.
- Any initialization routines within the DLL are called. (Our example has no initialization.)
- Java executes the statement: hw.displayHelloWorld();
- HelloWorld.displayHelloWorld() is a native method so its implementation is in the Delphi DLL(Java_HelloWorld_displayHelloWorld.)
- The Delphi code executes the WriteLn('Hello World!'); statement which displays the text Hello World! on the screen.
Whew. That sure seemed like a lot of work. Actually, the explanation of what is going on is far more verbose than the actual code. If youlook at the Java files, you'll see that each file has about 4 lines of Java code. The Delphi DLL source file contains about 10 or so(depending how you want to format the code.) There's really only 1 line of executable code in the DLL; the line that prints 'Hello World!' to the screen. It's important to realize that (in the graphic above) steps 1 - 5 are executed only once, regardless of how many times hw.displayHelloWorld() (or any other native method of the HelloWorld class) is called. This is similar to how DLLs work with regular executables (.exe) programs: the DLL is loaded and any overhead associated with the loading and initialization of the DLL is done once at load time.Also, this is just a very high-level view of what's going on. There are some pretty interesting things going on under the covers. But, that's beauty of it: you don't need to know or worry about those details.
For the most part, programming with the JNI is a pretty straight-forward process. However, there are a lot of details involved. As is true withmost programming, one missing piece can cause the whole program to fail. In this section, I will show you most of the mistakes that you arelikely to make. (I've made them all at one time or another!) Understanding the error messages and exceptions will help you to locate theproblems within your code. I was able to cause all of these errors by modifying the example above. Hence, some of the error messages will refer to thefiles in the HelloWorld example. If you are trying this example on your ownI highly encourage you to cause these errors to occur. You will see first-hand what I'm talking about and learn from these common errors.By far, the most popular is the java.lang.UnsatisfiedLinkError:java.lang.UnsatisfiedLinkError
There are basically two categories of UnsatisfiedLinkError errors.
- The first type of java.lang.UnsatisfiedLinkError looks something like:This message says that Java can't locate the HelloWorldImpl DLL. Here are some common causes:
- The DLL is named incorrectly. Make sure that the name of the DLL you are loading in the static section matchesthe name of the DLL on the disk. Be sure that you are not including the .dll extension in the call toSystem.loadLibrary:
- The DLL is not in the search path. In Windows (DOS) by default, the directory you are in (at a command prompt) is included in the search path. If you want Java to be able to find the DLL from another directory, make sure that you add that directory to the system path or move the DLL into a directory that is already in the system path.
- Another java.lang.UnsatisfiedLinkError error might look like this:This message indicates that the HelloWorldImpl DLL was located and loaded by the system. However, it says it can't find the indicated method(displayHelloWorld in the example.) There are several reasons that this might happen:
- The name of the method is incorrect. Make sure that the name in the DLL matches the name in the Java class exactly.You may have made one or more of these errors:
- You spelled the method name differently. (It happens!)
- The case is incorrect. Remember, Java is case-sensitive and Delphi is not, so you may have inadvertently used the wrong case.
- You decorated the method name incorrectly. It must start with Java_ then the class name HelloWorld, thenand underscore _, then the name of the method displayHelloWorld. (For classes other than our HelloWorld example, youwould substitute that class name in place of HelloWorld.)
Technically, the class name is the fully qualified name, including the package name. These examples are trivial, so packages are not used. If your Javaclass resides in a package, make sure to include the package name as part of the class name.
- The method is not exported. The method must be added to the exports section of the project file. (It is common to forget thiswhen you first start writing DLLs.)
Run-time exceptions
There are many ways that a native DLL can cause an exception. However, the errors I show are unique to JNI programming and not programming in general.For example, dividing by zero can cause an exception, but that isn't related to JNI programming.
It's also important to realize that the compilercan't help you here by detecting potential conflicts. This is because the Delphi compiler has no knowledge of the Java code and the Java compiler hasno knowledge of the Delphi code. Sure, we've mapped the types and decorated the function names, but there is no way for the compiler to check thatfor us. A tool like javah would help minimize the problems, but it still wouldn't be fool-proof.
Since these are Windows DLLs, exceptions are likely to cause a dialog box to be displayed (as opposed to the Java exceptions listed above that printed error messages to the screen.) Rather than describing these 'cryptic' error messages, I'll just insert screen shots so youcan see for yourself. (Note that the hex numbers and addresses may be different on your machine.)
Most of the exceptions generate similar error messages so it is usually difficult (if not impossible) to tell what causedthe error simply by looking at the dialog box. However, I'll point out some subtle clues that can help you locate the problems.
Check out our and!This guide is broken up into two segments, the first focusing on the essential 'Main Quest' or 'Story' missions, and the second focusing on the various optional that appear throughout the game.Main Quest Guides. Get help in. Fallout 4 can you save the hubologists. Escape and begin the search for Shaun. Encounter the. Rescue a gumshoe.
- Missing stdcall directive. The most common mistake made by Delphi programmers is forgetting to add the stdcall directive at the end of the procedure/function.(I'm guessing about the other Delphi programmers because, frankly, I haven't talked with any other Delphites about their JNI experiences. I'm justassuming that they would make this mistake because I made it often!)
A subtle difference in this exception as opposed to other exceptions is that the method call fails immediately. What I mean to say is that the callitself causes the exception. None of the code is executed within the native method. This can be helpful because some exceptions (as we'll see shortly)occur when the method returns, so all of the code within the method is executed. When I remove the stdcall directive in our example, the text Hello World! never gets displayed. This is an indication that the calling convention is incorrect.
This is the message I get when I remove the stdcall directive:
- Incorrect procedure/function signature. This happens when the number, type, and order of parameters in the Java class don't match the number, type, and order of the parameters in the Delphicode. In our example, the displayHelloWorld method was declared as taking 0 parameters. In the Delphi code, we made sure that the procedureexpected 0 parameters. (This is not counting the two hidden arguments I discussed that all JNI methods include.) If we add a parameter to theDelphi procedure (an extra integer parameter) we can force this error to happen:This is the error message I receive:
The subtle difference between this exception and the previous one (where I removed the stdcall directive) is that this time the textHello World! is displayed on the screen before the error message is displayed. It's subtle differences like this that can help you locatewhere the problems are.
- The name of the method is incorrect. Make sure that the name in the DLL matches the name in the Java class exactly.You may have made one or more of these errors:
This displays the Run Parameters dialog box:
In the Host Application edit box, enter the full path to java.exe. (Your path will probably be different.)
In the Parameters edit box, enter Main for our example.
At this point, you can set a break point on the WriteLn('Hello World!') statement in the Delphi Java_HelloWorld_displayHelloWorld procedure.Now, press F9 to run the program within the debugger. The debugger should stop on the WriteLn('Hello World!') statement.
Note that you will not be able to step through the Java code. The Delphi debugger has no knowledge of the Java code.
In this document, I showed you how easy it is to access code from a DLL that was created with Delphi. However, 95% of what I discussed applies equallywell to C/C++ or any other native language. This introduction to Using the Java Native Interface with Delphi is more of a tutorial thana reference. If you remove all of the detailed explanations above, you will see that the amount of code necessary to call a DLL from Java isquite trivial. Newcomers to Delphi, DLLs, and/or JNI may find this detail necessary and enlightening. More advanced developers may already understand these points.Before proceeding onto Part Two, I strongly encourage you to try this example on your own. The main reason is to prove to yourself that your machine is properly configured to compile and execute Java code. If you are currently able to do so, then it is likely that thisexample will work just fine. It's a lot easier to troubleshoot problems when the example is trivial.
Download the files used in this tutorial.
Using the Java Native Interface with Delphi (Part Two)
Using the Java Native Interface with Delphi (Part Three)
- JNI - Java Native Interface. This link points to the JNI specification set forth by Sun.Consider this the JNI Bible This link includes the specifications for both JDK 1.1 and JDK 1.2 aswell as a JNI FAQ. This is the place to start on Sun's page.
- Essential JNI: Java Native Interface (Essential Java) by Rob Gordon, Robert Gordon, Alan McClellan (Prentice Hall Computer Books, April 1998. ISBN 0136798950)
- The Java Native Interface: Programmer's Guide and Specification by Sheng Liang (Addison-Wesley Publishing Company, June 1999. ISBN 0201325772)
- Delphi in a Nutshell by Ray Lischner (O'Reilly and Associates, March 2000. ISBN 1-56592-659-5)
- Delphi 4 Developer's Guide by Xavier Pacheco, Steve Teixeira (Sams Publishing, August 1998. ISBN 0672312840)
- Project JEDI From their mission statement: 'An international community of Delphi developers with a mission toexploit our pooled efforts, experiences and resources to make Delphi--the greatest Windows development tool--even greater.'
- Pascal Header Translations by Charlie Calvert. This is geared towards achieving 'JEDI status.' There is a lot of information regarding translating C/C++ header filesto Delphi.
- Dr. Bob's Header Conversion I stumbled across this whilecreating these references. Looks cool!
- Marco Cantu's Essential PascalThis is from his Mastering Delphi series and is an excellent document on Delphi's Pascal-based language.
- Programming with the Java Native Interface This is a tutorial that shows how to use C++ withthe Java Native Interface on Linux, Solaris, and Windows NT.
- Using the Java Native Interface with C++. This document has a wealth of examples and techniques for using the JNI with C++. There is a detailed comparisonbetween Microsoft's implementation and Sun's implementation, including thedifferences in syntax and runtime performance. In addition to the basics, there are examples that demonstrate two-dimensional arrays, callbacks, and exceptions, among other concepts.