4.9. Writing Bundles That Contain Native Code

Because one of the intended uses of the residential gateway is to control low-level devices, in many circumstances we need to resort to native code to achieve certain functionality unavailable in the Java platform.

The framework allows a bundle to carry several dynamically linked native libraries for different operating system and processor environments, and at installation time attempts to match the appropriate native library with the underlying platform. If the framework succeeds in finding such a match, the native library will be linked into the Java virtual machine.

The framework learns the characteristics of the native platform from the OSGi environment properties established when the framework is started (Table 4.2).

Table 4.2. OSGi Environment Properties
Environment Property Meaning Example Value
org.osgi.framework.processor The processor of the gateway sparc
org.osgi.framework.os.name The operating system running on the gateway Solaris
org.osgi.framework.os.version The operating system version 2.6
org.osgi.framework.language The language used in the operating system en

Each bundle that carries native libraries must define a Bundle-NativeCode header in its manifest, which tells the framework which library to pick. For example, imagine a bundle that provides a service that returns the free space on a file system, a function implemented in native code. It may have the following declaration in its manifest:

Bundle-NativeCode: com/acme/impl/filesys/libfilesysinfo.so;
   osname=Solaris; processor=sparc; osversion=2.5,
   com/acme/impl/filesys/libfilesysinfo.so; osname=SunOS;
    processor=sparc; osversion=5.5,
   com/acme/impl/filesys/filesysinfo.dll; osname=Windows NT;
      processor=x86; osversion=4.0

This header contains three clauses separated by commas. It says that the bundle contains two native libraries: libfilesysinfo.so for the Solaris/SunOS system and filesysinfo.dll for Windows NT. Notice for some Sun's platforms, the operating system value is Solaris 2.x, whereas for others it reports SunOS 5.x. They effectively refer to the same environment, and we supply both as a precaution. Such situations are expected to be common in other operating environments as well, and this technique can be quite useful.

The following are more examples of how to define the Bundle-NativeCode header:

Bundle-NativeCode: libX; libY; libZ; processor=x86; osname=Linux,
 libA; libB; processor=x86; osname=Windows NT

This code says install libX, libY, and libZ on an x86 machine running Linux, but install libA and libB if the machine runs Windows NT. If you want a group of libraries to be installed on one particular platform, separate them with semicolons and place them at the beginning of the same clause.

The following code causes either libX or libY, but not both, to be installed on an x86 machine running Linux.

Bundle-NativeCode: libX; osname=Linux; processor=x86,
 libY; processor=x86; osname=Linux

The framework picks one arbitrarily between the two. If you want them both to be installed, declare libY in the same clause with libX. Note that the order in which the attribute specifications appear (osname or processor) in a clause does not matter.

The following code says install libX on an x86 machine with Linux version 6.0 or 6.1, but pick libY on an x86 machine running any of the three Windows variants.

Bundle-NativeCode: libX; osname=Linux; processor=x86;
 osversion=6.0; osversion=6.1,
 libY; processor=x86; osname=Windows NT; osname=Windows95;
 osname=Windows98

Note that if the same attribute specification (osversion and osname) is repeated in a clause, their values are OR'ed together during the matching process.

Now consider the following code:

Bundle-NativeCode: libX; osname=Linux; processor=x86;
 osversion=6.0,
 libY; processor=x86; osname=Linux; osversion=5.2,
 libZ; processor=x86; osname=Linux; lang=en

If an x86 machine runs Linux 6.0, libX will be chosen as the exact match. If it runs Linux 6.1, however, all three are valid candidates, because 6.0, 5.2, and no version number are considered backward compatible with (because they are less than) version 6.1: At this point, the next criterion we can use is the language setting. If the operating system language locale is set to English (en), libZ is the only winner. However, if the operating system language setting is Japanese (ja), then the framework will pick either libX or libY, because they have no specific requirement for the language.

If the machine runs Linux 5.0, then only libZ can be selected, because libX and libY require an operating system with incompatible version numbers, whereas libZ has no specific requirement on the operating system version number.

To summarize, the framework uses the algorithm presented in Figure 4.6 to find the “best” matching native code clause.

Figure 4.6. The algorithm that matches a clause in the Bundle-NativeCode header with the underlying native platform


Now let's look closer at the native code example, which implements a service that reports free disk space. Just like any other services, we begin by defining the service interface:

package com.acme.service.filesys;
/**
 * This service returns information about a given file system.
 */
public interface FilesystemInfoService {
   /**
    * Gets free space on the given file system.
    *
    * @param path the path indicates which file system to query; if
    * null is specified, the current directory is used.
    * @return the free space in bytes.
    * @exception java.io.IOException if low-level operations fail to
    * get the value of free space.
    */
   public long getFreeSpace(String path) throws java.io.IOException;
}

Next we provide the implementation class, defined as

package com.acme.impl.filesys;
import java.io.IOException;
import com.acme.service.filesys.FilesystemInfoService;

class FilesystemInfoServiceImpl implements FilesystemInfoService {
   public native long getFreeSpace(String path) throws IOException;

   static {
      System.loadLibrary("filesysinfo");
   }
}

The getFreeSpace method is declared to be native without an implementation body, and the native library is to be loaded and linked when the service implementation class is loaded.

The following code shows the C implementation on Solaris systems, which invokes the system call statvfs to obtain the free disk space information:

/*
 * The native implementation of the following method in class
 * com.acme.impl.filesys.FilesystemInfoServiceImpl:
 *    public native long getFreeSpace(String path)
 *       throws java.io.IOException;
 * The free disk space is obtained by calling statvfs system call on
 * Solaris systems. Refer to man pages for more details.
 */

#include <sys/types.h>
#include <sys/statvfs.h>  /* for statvfs system call */
#include <unistd.h>       /* for getcwd system call */
#include "utils.h"        /* for throw_exception */
#include "com_acme_impl_filesys_FilesystemInfoServiceImpl.h"

JNIEXPORT jlong JNICALL
Java_com_acme_impl_filesys_FilesystemInfoServiceImpl_getFreeSpace
(JNIEnv *env, jobject obj, jstring path)
{
   struct statvfs buf;  /* for saving the result of statvfs call */
   long availspace;
   const char *pathstr;

   if (path == NULL) {
      /* use the path to the current directory
        * if path is not specified
        */
      if ((pathstr = getcwd(NULL, 64)) == NULL) {
            /* throw java.io.IOException if getcwd fails */
            throw_exception(env, "java/io/IOException",
            "cannot get path to current directory");
            return -1;
      }
   } else {
      /* convert path from jstring to C string */
      pathstr = (*env)->GetStringUTFChars(env, path, NULL);
      if (pathstr == NULL) {
            /* OutOfMemory error has been raised */
            return -1;
      }
   }
   /* get the file system status */
   if (statvfs(pathstr, &buf) == 0) {
      /*
       * calculate free disk space: # of free blocks available to
       * non-super-user times block size in bytes
       */
      availspace = buf.f_frsize * buf.f_bavail;
   } else {
      /* throw java.io.IOException if statvfs fails */
      throw_exception(env, "java/io/IOException",
         "cannot get file system status");
      availspace = -1;
   }
   (*env)->ReleaseStringUTFChars(env, path, pathstr);
   return availspace;
}

The com_acme_impl_filesys_FilesystemInfoServiceImpl.h header file is generated by the javah command:

					javah -jni com.acme.impl.filesys.FilesystemInfoServiceImpl

It declares the function prototype for the getFreeSpace method, which by now is a little beyond recognition:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_acme_impl_filesys_FilesystemInfoServiceImpl
    */

#ifndef _Included_com_acme_impl_filesys_FilesystemInfoServiceImpl
#define _Included_com_acme_impl_filesys_FilesystemInfoServiceImpl
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:    com_acme_impl_filesys_FilesystemInfoServiceImpl
 * Method:   getFreeSpace
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL
Java_com_acme_impl_filesys_FilesystemInfoServiceImpl_getFreeSpace
(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

We add a utility function, throw_exception, into our utils.h header file so that its definition can be shared by the C implementation for both Solaris systems and Windows NT during compilation. Here is the contents of the header file:

#ifndef _utils
#define _utils

/*
 * Throw an exception.
 * param name: the name of the exception class; e.g.,
 * java/io/IOException
 * param msg: the message associated with the exception
 */
void throw_exception(JNIEnv *env, const char *name, const char *msg)
{
   jclass cls = (*env)->FindClass(env, name);
   if (cls != NULL) {
      (*env)->ThrowNew(env, cls, msg);
   }
   (*env)->DeleteLocalRef(env, cls);
}
#endif

The shared library can be created with the following command line, assuming the JDK software is installed at /jdk:

cc -G -I/jdk/include -I/jdk/include/solaris filesysinfo.c -o libfilesysinfo.so

Now we complete the picture with the Windows version, implemented by invoking the Win32 API GetDiskFreeSpace:

/*
 * The native implementation of the following method in class
 * com.acme.impl.filesys.FilesystemInfoServiceImpl:
 *    public native long getFreeSpace(String path)
 *       throws java.io.IOException;
 * The free disk space is obtained by calling WIN32 API
 * GetDiskFreeSpace. Refer to documentation for more details.
 */

#include <wtypes.h>   /* for DWORD */
#include <winbase.h>  /* for GetDiskFreeSpace */
#include "utils.h"    /* for throw_exception */
#include "com_acme_impl_filesys_FilesystemInfoServiceImpl.h"

JNIEXPORT jlong JNICALL
Java_com_acme_impl_filesys_FilesystemInfoServiceImpl_getFreeSpace
(JNIEnv *env, jobject obj, jstring path)
{
   DWORD sectorsPerCluster;
   DWORD bytesPerSector;
   DWORD numOfFreeClusters;
   DWORD totalClusters;
   const char *pathstr;
   long availspace;

   if (path == NULL) {
      /* NULL path name means current directory */
      pathstr = NULL;
   } else {
      /* convert path from jstring to C string */
      pathstr = (*env)->GetStringUTFChars(env, path, NULL);
      if (pathstr == NULL) {
            /* OutOfMemory error has been raised */
            return -1;
      }
   }
   /* get free disk space */
   if (GetDiskFreeSpace(pathstr, &sectorsPerCluster,
         &bytesPerSector, &numOfFreeClusters, &totalClusters)) {
      /* calculate free disk space */
      availspace = sectorsPerCluster * bytesPerSector *
         numOfFreeClusters;
   } else {
      /* throw java.io.IOException if GetDiskFreeSpace fails */
      throw_exception(env, "java/io/IOException",
         "cannot get free disk space");
      availspace = -1;
   }
   if (pathstr != NULL)
      (*env)->ReleaseStringUTFChars(env, path, pathstr);
   return availspace;
}

The Windows Dynamic Linked Library can be created with Microsoft Visual C++ development tools like this:

cl -Ic:jdkinclude -Ic:jdkincludewin32 -MD -LD filesysinfo.c-Fefilesysinfo.dll

where c:jdk is assumed to be the location where the JDK software is installed.

The bundle has the following layout:

META-INF/MANIFEST.MF
com/acme/impl/filesys/FilesystemInfoServiceImpl.class
com/acme/impl/filesys/filesysinfo.dll
com/acme/impl/filesys/libfilesysinfo.so
com/acme/impl/filesys/Activator.class
com/acme/service/filesys/FilesystemInfoService.class

Finally, we must set the native library search path for the operating system. If you are using a Java™ 2 Runtime Environment, you can specify the java.library.path system property at the startup of the Java Embedded Server software:

java -Djava.library.path=jes_cache_dir/libraries com.sun.jes.impl.framework.Main

where jes_cache_dir is the cache directory on your system.

If, however, you are using a JDK version 1.x the Java runtime environment, set the LD_LIBRARY_PATH environment variable for the Solaris system (and most of UNIX):

setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:jes_cache_dir/libraries

or set PATH for Windows NT:

path %PATH%;jes_cache_dirlibraries

We do not explain the C code in any depth because the Java™ Native Interface (JNI), UNIX system calls, and WIN32 programming are all beyond the scope of this book. However, we do want to present a not very trivial example to describe the entire process of developing service bundles with native code.

For details on JNI, please consult The Java™ Native Interface: Programmer's Guide and Specification by Sheng Liang [16].

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.170.63