HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

There are many situations in which it's useful or necessary to invoke native functions from Java. One of the more challenging is to invoke Microsoft COM functionality using Java native methods. Developers using Microsoft's J++ platform can sidestep the problem by creating a Java interface directly from the COM (OLB) files, but the resulting Java application may include Microsoft-specific language extensions. However, a platform-independent solution (at least in the Java sense) is available using the Java Native Interface. JNI provides a standard interface by which Java classes interface with native code and vice versa. In this article we'll develop a hybrid application using a Java GUI to invoke MS Excel functionality wrapped in a C++ load library (dll). While the code samples are specific to Excel (and, admittedly, of little practical use), the technique is extensible to any COM object.

Using the Java Native Interface
JNI is a specification for native code interfaces developed by Sun Microsystems and distributed as part of the JDK. A detailed description of the interface can be found in Beth Stearns's article at http://java.sun.com/docs/books/tutorial/native1.1/index.html, but, in summary, using the JNI, you can:

  • Call applications and functions written in C, C++ or Assembler from Java. In this example we'll use native code to create an instance of an Excel application, open a workbook and worksheet, insert some data and chart it.
  • Call Java functions from native code. Our example will request chart data values from a Java Option Pane.
While additional JNI features are covered in the Sun tutorial, they won't be used in this example.

Using the JNI requires the following steps:

  1. Create the Java class or classes that will act as the native code interface. Our example creates the AXExcel class, which defines the Java methods that will be implemented in native code. The AXExcel class is then compiled to the AXExcel.class file.
  2. With AXExcel.class as input, run the javah utility (included in the JDK) with the -jni option to create a C-style header file -in this case, AXExcel.h.
  3. Create a dynamic library (dll) implementing the functions in AXExcel.h. The native code in this example will be implemented in AXExcel.cpp. This is by far the most time-consuming step.
  4. Build and link the dynamic library as AXExcel.dll. This library must be accessible to the Java AXExcel class.
  5. Create a simple Java GUI interface to invoke the native methods (see Figure 1).
  6. Test and debug the application.

Figure 1
Figure 1:

This example was developed using Symantec Visual Café Professional V3.0 and MS Visual C++ 6.0 running on a WinNT 4.0 SP5 system. Win95 platform developers may need to install DCOM to support the COM features.

Creating the Java Native Class
Listing 1 contains the definition for the AXExcel class. Java native methods can be declared in any class, but the application will be simpler if the native interface is confined to a single class. AXExcel.java defines four native methods:

  • OpenExcelWB creates a new Excel workbook and activates a worksheet.
  • SetVisibility allows the Java interface to hide or show the worksheet.
  • ExcelGraph creates a bar graph from values supplied via getCellValue.
  • QuitExcel saves the worksheet and closes the Excel application.

Note that when the method modifier native is used, the method has no implementation. Java expects to find the implementation in a system load library (dll) that is loaded prior to any call to a native method. This can be done during object construction, but the more general practice is to use a static initializer as shown in Listing 1.

The final method in AXExcel is getCellValue, which is called by ExcelGraph to obtain values for the bar graph. getCellValue raises an option pane to allow the user to supply values. Entering a null value signals ExcelGraph to stop requesting values and create the graph.

Next, having compiled AXExcel.java to AXExcel.class and corrected any compilation errors, we can generate the AXExcel.h header file using the javah utility. From the command line, javah -jni AXExcel creates the header file needed for the C++ native implementation.

Creating the C+++ Native Implementation (AXExcel.dll)
The starting point for implementation is the AXExcel.h header file shown in Listing 2. C++ programmers will immediately note the unique JNI types, which are defined in the jni.h file usually found in the ..\java\include folder. For a description of the JNI types, extractors and other operators, refer to the JNI section of the Java Tutorial. Our task is to implement each of these in a Win32 dll using the Excel COM interface. To simplify library dependencies, we won't use MFC.

Listings 3, 4 and 5 show the source code for AXExcel.cpp. In Listing 3 the necessary includes and imports are shown along with the DllMain function. The three import files provide the COM functionality for Excel and must be located in directories in the Include paths.

The full syntax of import statements is as follows (see comments for Office 2000 in Listing 3):

#import <mso97.dll> no_namespace rename("/DocumentProperties",
"DocumentPropertiesXL")
#import <vbeext1.olb> no_namespace
#import excel8.olb> rename(DialogBox", "DialogBoxXL")
rename("RGB", "RBGXL") rename("DocumentProperties",
"DocumentPropertiesXL") no_dual_interfaces

Next, the global IDispatch COM pointers for the Application, Worksheet(s) and Workbook are declared. The DllMain function is required to ensure that:

  • The COM Library concurrency model is initialized as Multithreaded on attachment. The default is ApartmentThreaded, which will cause runtime errors.
  • The COM library is detached at termination.
OpenExcelWB (Listing 4) demonstrates a number of important concepts. The first task is to extract the path and worksheet names from the jstring arguments using the JNI extractor GetStringUTFChars. Note that the result is a standard C-style string buffer that must be released with the ReleaseStringUTFChars method before the pointer loses scope. Next, we look for the open Excel application or, finding none, create a new Excel task, which sets the application dispatch pointer, pXL. This dispatcher is used to retrieve the Workbooks collection. If the path and wks arguments reference an existing workbook and worksheet, it will be opened in the next try block. Otherwise the resulting error will be caught and a new workbook and worksheet will be created. The method completes by activating the current worksheet, making Excel visible and releasing the buffers used for extraction.

SetVisibility is interesting primarily as a demonstration of variations of the Boolean type. JNI uses jboolean, with values JNI_TRUE and JNI_FALSE. Setting Excel's visible property requires a variant type with the VARIANT_TRUE or VARIANT_FALSE value.

QuitExcel's main functions are to save the workbook and exit the Excel Application, both of which are accomplished in the first try block. However, the last four statements are critical: unless the dispatch pointers are released and detached, the application won't terminate properly.

ExcelGraph method (Listing 5) is the most complex implementation in that it not only creates an Excel chart, it retrieves the data by calling the Java method, getCellValues. After clearing the cells and initializing the range to the cell A1, we locate the Java class with the GetObjectClass method, which returns a jclass object, caller. GetMethodID uses the class to locate getCellValues on the Java side, returning a jmethodID object method. Locating the method requires the class, a C-style string containing the method name, and the exact Java method signature. The JNI is unforgiving on this point, so it's a good idea to use javap with the -s option to get the precise syntax. For example, javap -s AXExcel displays (Ljava/lang/String;)Ljava/lang/String; for getCellValues, the necessary third parameter for getMethodID. If a valid method ID is returned, the do-while loop iterates until an empty (0-length) string is returned. First, it retrieves the cell ID, using the Range method GetAddressLocal, and creates a jstring representation of it for use as the getCellValue argument. We then call the Java getCellValue method, which should return a numeric value as a jstring. After conversion to a C-string this is inserted into the current cell, the active cell range is moved one cell to the right and the buffer is released. When the do-while loop terminates, the entire UsedRange is used to create a simple bar chart. The method terminates by releasing and detaching the range and chart dispatch pointers.

Compiling, Building and Debugging the dll
The example code was compiled as an MS Visual C++ 6.0 Win32 Dll project. In addition to the default preprocessor options, it's necessary to add _OLE32_ and _WIN32_DCOM to activate the automation options. During code debug it's helpful if the C++ project folder and dll target directory are the same as the Java class directory.

Debugging a hybrid application can be tricky if neither component is known to work. One approach is to design a minimal driver interface like the one shown in Figure 1. In this example the ExFrame constructor creates an instance of AXExcel, and the button ActionEvent handlers use that instance to call the native methods. The Java side can be debugged by building a C++ dll version with "stub" methods, i.e., methods that do nothing except provide valid return values. With a visual debugger like Symantec's Visual Cafe, the calls to the native methods can be confirmed before you tackle the intricacies of the COM code. On the C++ side, since AXExcel.dll is loaded by the AXExcel class, it must be debugged by executing jave.exe with the appropriate driver class. In this example the Visual C++ debug executable would be:

<Javapath>\Java\bin\java.exe ExFrame

Application
While this example is admittedly somewhat illogical (e.g., why call back to Java to perform data entry to a visible spreadsheet?), it raises some intriguing potential for reusing user-developed COM objects in addition to the standard COM interfaces like those provided by Microsoft Excel and Word. Theoretically, any COM object could be wrapped in a similarly constructed dll and called from Java via the JNI interface.

Author Bio
Allan K. Green owns and operates QualiNet Company in Hillsborough, North Carolina, providing training and consulting services in OO A&D design tools and languages, principally C++ and Java, custom courseware and instruction programs, and systems design and integration services. His previous experience includes information systems management and development for [email protected]
He can be reached at: [email protected]

	

Listing 1: AXExcel.java
 
import java.lang.*; 
public class AXExcel extends java.lang.Object { 
static { 
         try { 
                System.loadLibrary("AXExcel"); 
        } 
         catch (UnsatisfiedLinkError e)  { 
                System.out.println(e.getMessage()); 
         } 
         } 
public String getCellValue(String cellName) { 
   String inputValue = 
      JOptionPane.showInputDialog("Cell" +  cellName+ ":"); 
      return inputValue; 
   } 
 public native boolean ExcelGraph(); 
 public native boolean OpenExcelWB(String path,String wks); 
 public native void SetVisibility(boolean flag); 
 public native void QuitExcel(); 
} 
  
  

Listing 2: AXExcel.h
 
#include <jni.h> 
#ifdef --cplusplus 
extern"C"{ 
JNIEXPORT jboolean JNICALL Java_AXExcel_OpenExcelWB 
  (JNIEnv *, jobject, jstring, jstring); 
  

JNIEXPORT void JNICALL Java_AXExcel_SetVisibility 
  (JNIEnv *, jobject, jboolean); 
  

JNIEXPORT void JNICALL Java_AXExcel_QuitExcel 
  (JNIEnv *, jobject); 
  

JNIEXPORT jboolean JNICALL Java_AXExcel_ExcelGraph 
  (JNIEnv *, jobject); 
#ifdef --cplusplus 
} 
  
  

Listing 3: Necessary Includes, Imports and DllMain Function
 
#include "AXExcel.h" 
#include <comdef.h> 
#include <stdio.h> 
#include <objbase.h> 
#include <windows.h> 
#import <mso97.dll>     //mso9.dll for MS Office 2000 
#import <vbeext1.olb>   //vbe6ezt.olb for VB6 
#import <excel8.olb>    //excel9.olb for MS Office 2000 
#pragma warning (disable:4192) 
using namespace Excel; 
_WorksheetPtr pSheet; 
SheetsPtr pSheets; 
_WorkbookPtr pBook; 
_ApplicationPtr pXL; 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, 
DWORD fdwReason, 
       LPVOID lpReserved ) 
{   switch( fdwReason ) 
{   case DLL_PROCESS_ATTACH: 
       CoInitializeEx(NULL,COINIT_MULTITHREADED); 
       break; 
    case DLL_PROCESS_DETACH: 
       CoUninitialize(); 
       break; 
      }    return TRUE; 
} 
  
  

Listing 4: OpenExcelWB
 
JNIEXPORT jboolean JNICALL Java_AXExcel_OpenExcelWB 
  (JNIEnv *jThis, jobject jObj, jstring path, jstring wks) 
{ 
        const char* jbuf = jThis->GetStringUTFChars(path,0); 
        const char* wkname = jThis->GetStringUTFChars(wks,0); 
        jboolean rc = JNI_FALSE; 
        try { 
                if (pXL.GetActiveObject("Excel.Application.8")) { 
                        pXL.CreateInstance("Excel.Application.8"); 
                } 
                WorkbooksPtr pBooks = pXL->Workbooks; 
                try { 
                        pBook = pBooks->Open(jbuf); 
                        pSheets = pBook->GetSheets(); 
                        pSheet = pSheets->GetItem(wkname); 
                        rc = JNI_TRUE; 
                } 
                catch (_com_error &) { 
                        pBook = pBooks->Add(); 
                        pSheet = pBook->Sheets->Add(); 
                        pSheet->Name = wkname; 
                        rc = JNI_TRUE; 
                } 
                pSheet->Activate(); 
        } 
        catch (_com_error &e) { 
                printf("Error: %d, Msg: %s",e.Error(),e.ErrorMessage()); 
        } 
        Java_AXExcel_SetVisibility (jThis, jObj, true); 
        jThis->ReleaseStringUTFChars(path,jbuf); 
        jThis->ReleaseStringUTFChars(wks,wkname); 
        return rc; 
} 
JNIEXPORT void JNICALL Java_AXExcel_SetVisibility 
                (JNIEnv * jThis, jobject jObj,jboolean flag){ 
        try     { 
                if (pXL != NULL){ 
                    if ( flag == JNI_TRUE)  pXL->Visible = VARIANT_TRUE; 
                        else pXL->Visible = VARIANT_FALSE; 
                } 
        } 
        catch(_com_error &e) { 
                printf("Error: %d, Msg: %s",e.Error(),e.ErrorMessage()); 
    } 
} 
  

JNIEXPORT void JNICALL Java_AXExcel_QuitExcel 
  (JNIEnv *, jobject) { 
        try { 
                if (pXL != NULL) { 
                        pBook->Saved = VARIANT_TRUE; 
                        pXL->Quit(); 
                } 
        } 
        catch(_com_error &e) { 
                printf("Error: %d, Msg: %s",e.Error(),e.ErrorMessage()); 
    } 
        if (!pSheet->Release()) pSheet.Detach(); 
        if (!pSheets->Release()) pSheets.Detach(); 
        if (!pBook->Release()) pBook.Detach(); 
        if (!pXL->Release()) pXL.Detach(); 
} 
  
  

Listing 5: ExcelGraph Method
 
JNIEXPORT jboolean JNICALL Java_AXExcel_ExcelGraph 
          (JNIEnv *jThis, jobject jObj){ 
        try     { 
                pSheet->UsedRange->Clear(); 
                RangePtr pRange = pXL->ActiveCell; 
                int textlen; 
                jclass caller = jThis->GetObjectClass(jObj); 
                jmethodID meth = jThis->GetMethodID(caller, 
                        "getCellValue", 
                        "(Ljava/lang/String;)Ljava/lang/String;"); 
                if (meth == 0) { return JNI_FALSE;} 
                do { 
                        _bstr_t cellId = 
                        pRange->GetAddressLocal(false,false,xlA1,false); 
                        jstring range = jThis->NewStringUTF(cellId); 
                        jstring value = 
                          (jstring)jThis->CallObjectMethod(jObj, meth, range); 
                        const char* jbuf = 
                          jThis->GetStringUTFChars(value,0); 
                        textlen = jThis->GetStringUTFLength(value); 
                        pRange->Value = jbuf; 
                        jThis->ReleaseStringUTFChars(value,jbuf); 
                        pRange = pRange->Next; 
                } while (textlen != 0); 
                pRange  = pSheet->GetUsedRange(); 
                _ChartPtr  pChart  = pBook->Charts->Add(); 
                pChart->SetSourceData 
                        (pRange,&variant_t((long)xlColumns)); 
                pChart->Visible; 
                if (!pRange->Release()) pRange.Detach(); 
                if (!pChart->Release()) pChart.Detach(); 
        } 
        catch(_com_error &e) { 
                printf("Error: %d, Msg: %s",e.Error(),e.ErrorMessage()); 
    } 
        return JNI_TRUE; 
} 
  
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.