The NLM Side of the API_Info 24 Java/NLM Gateway Example
Articles and Tips: article
Senior Research Engineer
Developer Information
01 Feb 1998
Offers code for the NLM that acts as a gateway between the Java applet and application that runs in the client and the NLM to be used. Also discusses the use of make files in NLM development.
- Introduction
- The Java Native Interface
- Exposing API_Info's Services
- The Native Method Gateway: NMGATE.C
- MAKEFILE
- Make Files and NLM Developement
Introduction
In last month's issue of Novell Developer Notes, Larry Fisher gave you part of an application that extends NetWare-based services (implemented as NLMs) to client workstations running a browser. These browser clients do not require a connection to the server on which the NLM is running, and do not even have to have NetWare client software installed. In this DevNote, I will give you the rest of the story: the code for the NLM that acts as a gateway between the Java applet and application (the part that Larry gave you in last month's article) and the NLM whose services you want to expose. This gateway is written in Java, using JNI (the Java Native Interface). By special request, I will also talk a little bit about the use of make files in NLM development.
To keep things simple, we'll use the API_INFO application as the NLM whose services we will expose. API_INFO is a simple flat-file database implemented as an NLM. You can read about API_INFO in "The Anatomy of a Simple Client-Server Application," parts 1, 2, and 3, in the September, October, and November issues of Novell Developer Notes.
Figure 1: API_INFO application with Java/NLM gateway.
Larry described the overall design of the application in the article entitled "Extending Your NLM System into Java, With Ease." This design is summarized in Figure 1.
The code for the API_INFO applet is in Larry's article, "The Java Side of the API_INFO Java/NLM Gateway," in last month's issue.
The Java Native Interface
Native methods allow you to extend Java's functionality, making it possible to perform platform-specific activities that cannot be supported in Java. Using native methods, you can use computer resources that would otherwise be unavailable to the Java application. The Java Development Kit (JDK) 1.0 had a process for using native methods, but it suffered from several drawbacks.
First of all, Sun did not specify how objects should be laid out in memory, so each vendor's implementation of the Java Virtual Machine (JVM) did it differently.
This meant that programmers had to recompile their native method libraries for each platform they were to run on. Second, the garbage collection routine did not assure that unused native methods were removed from memory.
Sun provided an improved native-method interface in JDK 1.1, known as the Java Native Interface (JNI). There are a number of books that give more complete information about implementing native methods than can be given here. Using JNI to call native code implementations is straightforward.
When using a native method implemented using JNI, the only difference is that a native method is declared with the keyword native, and is loaded into the Java application before being used. The native method can be written in C, C++, or assembly language, and its first two parameters must be a pointer to the JNI environment and a reference parameter.
The JNI environment pointer is used to access and pass objects to the Java application. The reference parameter can refer either to a method of a Java class, or to an object.
In the case of our example application, the idea is to give browser clients access to NLM functions. Many developers have made a considerable investment in developing and optimizing NLMs.
Understandably, they would not be anxious to rewrite their existing (debugged, optimized) NLM code in Java. And many of the functions performed by NLMs would be difficult to recreate in Java. This is the perfect application for JNI.
The steps we will take to implement the gateway to API_Info's services are:
Export the appropriate function from API_Info.
Implement the native method (an NLM written in C, called NMGate) that passes requests from client applets to the API_Info NLM and returns replies to those requests from the API_Info NLM back to the requesting client applet.
Implement a Java class that loads the native method, listens on a socket for client applet requests, then forwards those requests to the native method and returns replies back to the client applet.
Exposing API_Info's Services
The API_Info function that processes client requests is called "NCPExtensionHandler." The NMGate NLM can only call another NLMs functions if that NLM has exported them. When we compiled API_Info, we didn't have the foresight to export the NCPExtensionHandler function.
So, in order to extend API_Info's services to browser clients, we first have to relink API_Info, exporting the NCPExtensionHandler function, then redepoly the relinked NLM. Fortunately, no other changes to the code is necessary, so no testing of the modified code will be required.
The Native Method Gateway: NMGATE.C
The gateway NLM is essentially a library NLM. It consists of one exported function that passes database requests to API_Info, then passes the retrieved data back to APIApp, which forwards it on to the requesting client. The NLM's code is just about that simple. A few things to notice in the code:
Header files jni_md.h, javaString.h, and jni.h provide the Java-related declarations used by the NLM.
The main function doesn't do anything--just puts the main thread to sleep, so the gateway function (Java_APIConnection_NMGate) can remain available to requesting clients.
The NLM uses the JNI functions GetStringUTFChars and NewStringUTF to get the request from the environment passed to the function and to return the reply obtained from API_Info, respectively.
The request from APIApp is passed as two parameters to Java_APIConnection_NMGate, a jint and a jstring. The reply is a string value returned by the Java_APIConnection_NMGate function.
One minor annoyance you will notice when developing an NLM based on JNI: a native method NLM has interdependencies with the Java Virtual Machine (JAVA.NLM), so to unload the NLM, you must also exit the Java Virtual Machine.
The code for NMGate.NLM is shown below:
#include <jni_md.h> #include <javaString.h> #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <nwtypes.h> #include <nwconio.h> #include <nwerrno.h> #include <nwsemaph.h> #include <nwthread.h> #include <nlm\nwncpext.h> #include <string.h> #include "api_hdr.h" #define DEBUG 1 extern BYTE NCPExtensionHandler(NCPExtensionClient * client, void * req, LONG reqLen, void * rep, LONG * repLen ); void main (void) { ExitThread (TSR_THREAD,0); } JNIEXPORT jstring JNICALL Java_APIConnection_NMGate (JNIEnv *env, jobject obj, jint req, jstring inString) { msg2Srvr in; msg2Client out; int rc; jstring retString; NCPExtensionClient client = {0,0}; //get pointer to inString const char *str = (*env)->GetStringUTFChars(env, inString, 0); #ifdef DEBUG //display what request we received ConsolePrintf("NMGate: Request received: %d, %s\n", req, str); #endif in.function = req; strcpy(in.data, str); rc = NCPExtensionHandler(&client, (void *)&in, sizeof(in), (void *)&out, sizeof(out)); #ifdef DEBUG //display return code and number of bytes of data returned by NCPExtensionHandler ConsolePrintf("NMGate: NCPExtensionHandler returned %d and %d chars of data\n", rc, strlen(out.data)); #endif retString = (*env)->NewStringUTF(env, out.data); return retString; }
MAKEFILE
If you've never developed in the NLM or Java environments before, getting all the tools and SDK set up and working right can be half the work. To ease the process as much as possible, I've included the Makefile for NMGate and APIApp below. More information about make files follows the listing.
.SUFFIXES: .SUFFIXES: .c .obj #Stuff you need to set for your environment # the JDK directory JDKDIR = j:\prog\jws\jdk # Java directory on the Intranetware server NWJAVABASE = j:\java # base directory for Watcom C/C++ WATCOMBASE = i:\prog\watcomc # base directory for the NLM SDK NLMSDKBASE = i:\prog\nwsdk10\nwsdk # classpath setting for JAVAC CLASSPATH = -classpath .;$(JDKDIR)\lib\classes.zip PRELUDE = prelude.obj CWAT = $(WATCOMBASE)\binnt\wcc386 CBASEFLAGS = -DWATCOM -DWATCOM11 -DNETWARE -D__NO_MATH_OPS -D_X86_ -Dx86 COPTS = /zp=1 /ei /ri /d2 /4s /w1 /s /zq /ez /od CWATFLAGS = $(CBASEFLAGS) $(COPTS) $(WATINCLUDES) LINK = $(WATCOMBASE)\binnt\wlink WATINCLUDES = \ -I$(NWJAVAINC) \ -I$(NLMSDKBASE)\INCLUDE \ -I$(NLMHEADERS) \ -I$(NWJAVAINC)\netware \ -I. JAVAIMP = $(NWJAVABASE)\include\netware\java.imp NWJAVAINC = $(NWJAVABASE)\include NLMIMPORTS = $(NLMSDKBASE)\imports NLMHEADERS = $(NLMSDKBASE)\include\nlm NLMVERSION = 1.0 JAVAC = $(JDKDIR)\bin\javac APIApp.class : APIApp.java $(JAVAC) $(CLASSPATH) APIApp.java nmgate.obj : nmgate.c $(CWAT) $(CWATFLAGS) nmgate.c -Fo=nmgate.obj nmgate.nlm : APIApp.class nmgate.obj $(LINK) @<< Form Novell NLM 'JNI Example (based on 1.1)' Name NMGate Option Version = $(NLMVERSION) Option Copyright '(C) Copyright 1997 Novell, Inc. All Rights Reserved.' Option Caseexact Option Map, Verbose, screenname 'Default' Option SYMFILE=nmgate.sym debug all debug novell module clib, mathlib, tcpip, api_info Import _SetupArgV_411 Import NCPExtensionHandler Import @$(NLMIMPORTS)\clib.imp, @$(NLMIMPORTS)\threads.imp Import @$(NLMIMPORTS)\nlmlib.imp, @$(NLMIMPORTS)\socklib.imp Import @$(NLMIMPORTS)\nit.imp, @$(NLMIMPORTS)\requestr.imp Import @$(JAVAIMP) Export Java_APIConnection_NMGate file $(NLMIMPORTS)\$(PRELUDE), nmgate.obj << clobber : del APIApp.class del nmgate.obj del nmgate.nlm del nmgate.map del nmgate.sym
Make Files and NLM Development
I've had a request to write a little bit about the use of make files in NLM development. Make files are a relic of the command-line worlds of DOS and (originally) Unix. Programmers who come to NLM development from the Mac (or other GUI) environments, or who have done all their development in integrated development environments (IDEs) may not have had the pleasure of working with make files.
Make files are text files that describe the relationships between source files and target files, and the actions that must be taken to generate new target files when a source file has been modified.
The make utility compares the timestamp on the source and target files to see if the target was generated after the source was last modified. If the timestamp on the source file is later than the target's timestamp, the target file is not current, and needs to be regenerated.
Versions
There are many versions of the make utility. If you are developing NLMs, you probably have Watcom C, which comes with the wmake utility. Microsoft Visual C/C++ has a version of make called nmake. Although all versions of make work basically the same, they have different command-line options and directives, so you should check the documentation for your particular version before using these features.
Make files contain
directives
macros
dependency declarations
Directives
Directives, sometimes referred to as "dot directives" are similar to command-line options, in that they control the general behavior of the make utility. Many directives correspond directly to command-line options.
Nearly all versions of make have a directive called .SUFFIXES (Microsoft nmake) or .EXTENSIONS (Watcom wmake). This directive allows you to specify which extensions are allowed in source and object file names, and what order the various file types are used. For example, the directive
.SUFFIXES: .c .obj
implies that .obj files are compiled from .c source files. A .SUFFIXES or .EXTENSIONS directive with an empty list clears any previously-defined rules.
Macros
Macros in make files are similar to preprocessor declarations. They define a symbolic value that represents a string in the make file. Macros improve make files by making them easier to read and maintain. You declare a macro by equating the symbolic value with the string it is to represent:
NWJAVABASE = j:\java
You reference the macro by preceding the symbol with a $:
JAVAIMP = $(NWJAVABASE)\include\netware\java.imp
Note that a macro declaration can contain a reference to another macro. The parentheses are optional, but preferred, since they eliminate potential ambiguities.
Dependency Declarations
Dependency declarations are where all the work is done in a make file. They specify the relationships between target and source files, and the commands that must be executed when a target file needs to be brought up to date. Consider, for example, the following declaration:
nmgate.obj : nmgate.c $(CWAT) $(CWATFLAGS) nmgate.c -Fo=nmgate.obj
This states that when NMGATE.C is changed, the make utility must run the Watcom C compiler to generate NMGATE.OBJ.
Symbolic dependencies are targets without any dependents. These are used to perform tasks like printing, deleting, or backing up files. For example, the clobber dependency deletes all object files in the NMGATE project:
clobber : del APIApp.class del nmgate.obj del nmgate.nlm del nmgate.map del nmgate.sym
Make Files and NLMs
There's nothing unique about a make file that makes NLMs. One feature of make files that comes in particularly handy when developing NLMs is the ability to put linker directives and options into the make file, rather than maintaining a separate linker directive file.
For example, in NMGATE's make file, NMGATE.NLM is dependent on APIApp.class and NMGATE.OBJ. When NMGATE.NLM needs to be updated, the make utility is directed to run the Watcom linker (Wlink), and the makefile passes a long list of linker directives and options to Wlink:
nmgate.nlm : APIApp.class nmgate.obj $(LINK) @<< Form Novell NLM 'JNI Example (based on 1.1)' Name NMGate Option Version = $(NLMVERSION) Option Copyright '(C) Copyright 1997 Novell, Inc. All Rights Reserved.' Option Caseexact Option Map, Verbose, screenname 'Default' Option SYMFILE=nmgate.sym debug all debug novell module clib, mathlib, tcpip, api_info Import _SetupArgV_411 Import NCPExtensionHandler Import @$(NLMIMPORTS)\clib.imp, @$(NLMIMPORTS)\threads.imp Import @$(NLMIMPORTS)\nlmlib.imp, @$(NLMIMPORTS)\socklib.imp Import @$(NLMIMPORTS)\nit.imp, @$(NLMIMPORTS)\requestr.imp Import @$(JAVAIMP) Export Java_APIConnection_NMGate file $(NLMIMPORTS)\$(PRELUDE), nmgate.obj <<
There are a lot of obscure features that can be used in make files and with the various make utilities, but it's probably a good idea to avoid them. Maintaining a make file is a lot like maintaining a program source code file. Even when it's your own file, it's a lot easier when the file is readable and the programmer's intentions are clear.
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.