Blog

Building an Excel add-in for HBase MapReduce

Summary

This latest project from JNBridge Labs investigates building an Excel add-in for Hadoop HBase. As a Java framework, HBase applications must use Java APIs, resulting in single-platform solutions. A cross-platform HBase integrated solution, particularly one that provides business intelligence on the desktop, like Microsoft Excel, is unable to leverage the HBase remote client API. This means using a lower level interoperability mechanism, like implementing a .NET Thrift client. The current project uses JNBridgePro for .NET-to-Java interoperability. It also leverages concepts and code from the previous lab, Building a LINQ provider for HBase MapReduce, which investigated a LINQ extension for HBase.  

Introduction

Hadoop allows businesses to quickly analyze very large data sets. Hadoop can reduce ludicrous amounts of data to a meaningful answer in a short amount of time, however, without understanding the shape of your data, you run the risk of garbage in, garbage out. Analysis itself is an iterative process relying on investigation. Tools that aid data investigation provide a means to quickly view, sort, filter/reduce and represent data, making it possible to quickly find and understand patterns, trends and relationships.

Microsoft Excel has always been the ubiquitous off-the-shelf  tool for data analysis and it makes a ready-to-go front end for Hadoop. Excel can be extended using add-ins developed in Visual Studio using VSTO, Visual Studio Tools for Office. This lab will explore a simple Excel front-end to HBase MapReduce. The front-end will allow a user to view HBase tables and execute MapReduce jobs. The goal is to make the add-in generic with respect to the column definitions and data in a HBase table.

Getting Started

The components required for this lab are identical to those required in the previous lab, Building a LINQ provider for HBase MapReduce. Here’s a quick list of the components.

  1. Apache Hadoop Stack (see the previous lab’s Getting Started section for more information)
  2. Visual Studio 2012
  3. Eclipse
  4. JNBridgePro 7.0
  5. Office Developer Tools for Visual Studio 2012 (this includes VSTO).
  6. Microsoft Office 2010

Calling Java from .NET: Creating proxies using JNBridgePro

Since the Excel add-in is written in C#/.NET and needs to call several Java class APIs, the first step is to use the JNBridgePro plug-in for Visual Studio to create an assembly of proxies that represent the Java API. When a proxy of a Java class is instantiated in .NET, the real Java object is instantiated in the Java Virtual Machine. The JNBridgePro run-time manages communications, i.e. invoking methods, and syncing garbage collection between the .NET CLR and the JVM.

For this development step, as well as during run-time, a bunch of Hadoop, HBase and ZooKeeper JAR files must be available on the Windows machine. These can be scraped from a machine running the Hadoop stack (look in /usr/lib/hadoop/lib/usr/lib/hbase/lib, etc.)

This is a screen shot of the Edit Class Path dialog for the JNBridgePro Visual Studio plug-in.

These are the JAR files required to create the .NET proxies. During run-time, three additional JAR files must be included in the JVM’s class path when initiating the bridge between the JVM and the CLR: avro-1.5.4.jarcommons-httpclient-3.1.jar and slf4j-nop-1.6.1.jar (the last JAR file inhibits logging by Hadoop and HBase).

Below, is a screen shot of the JNBridgePro proxy tool in Visual Studio. The left hand pane shows all the namespaces found in the JAR files shown in the above dialog. The required namespaces are org.apache.hadoop.hbase.client and org.apache.hadoop.hbase.filter. In addition, individual classes like org.apache.hadoop.hbase.HBaseConfiguration are required (see the link at the end of this blog to download the source).

 

By clicking on the Add+ button, the chosen classes, as well as every dependent class, will be found and displayed in the center pane. The right-hand pane displays the public members and methods of the Java HTable class. The last step is to build the proxy assembly, DotNetToJavaProxies.dll.

Creating and populating an HBase Table

It would be nice to have an HBase table loaded with data and provide an opportunity to test calling various HBase Java APIs from .NET. The simple data will consist of an IP address, like “88.240.129.183″ and the requested web page, for example “/zebra.html”. This lab will use the same table, access_logs, created for the previous lab, Building a LINQ provider for HBase MapReduce. Please see the previous lab’s section, Creating and populating an HBase Table, for the code used to build this table.

Building an Excel add-in

The Excel add-in will consist of a single control pane. As the user interacts with the pane, underlying code accesses the Excel data model consisting of workbooks, worksheets and charts. Here’s what the completed add-in looks like.

The class HBasePane is a .NET User Control. It consists of two groups, View Table and Map Reduce. The above screen shot shows the user controls labeled Zookeeper Host, Table Name and Number of Records, which all have user entered values. By clicking on the button, View Records, the user has loaded in 20 rows from the HBase table, access_logs.

Here’s the handler code for the button click event.

        private void viewTableButtonClick(object sender, EventArgs e)
        {
            Excel.Worksheet activeWorksheet 
                 = ((Excel.Worksheet)Globals.ExcelHBaseAddIn.Application.ActiveSheet);
            activeWorksheet.Name = "Records";
            Excel.Range navigator = activeWorksheet.get_Range("A1");
            int numRows = Decimal.ToInt32(this.numberOfRecords.Value);
            // most of the work done here
            this.columns = ViewHBaseTable.populateWorkSheet(navigator
                , this.hostName.Text
                , this.tableName.Text
                , numRows);
            // autofit the range
            int numCols = this.columns.Count<string>();
            Excel.Range c1 = activeWorksheet.Cells[1, 1];
            Excel.Range c2 = activeWorksheet.Cells[numRows, numCols];
            this.cols = activeWorksheet.get_Range(c1, c2); 
            this.cols.EntireColumn.AutoFit();
            // populate the user controls with the column names
            this.filterComboBox.Items.AddRange(this.columns);
            this.frequencyComboBox.Items.AddRange(this.columns);
        }

All the work is done in the method, ViewHBaseTable.populateWorkSheet(). The user controls are hostName, tableName and numberOfRecords. The hostName control contains the address of the machine that’s running Zookeeper, which is responsible for managing connections from the HBase client API. Below is code from populateWorkSheet(). Notice that the HBase table column family and cell names are obtained using the methods getFamily() and getQualifier() along with the cell values. The method returns an array of strings that represents the column and cell names in the table. These are used to populate the combo box controls filterComboBox and frequencyComboBox in the group Map Reduce.

            Configuration hbaseConfig = HBaseConfiguration.create();
            hbaseConfig.set("hbase.zookeeper.quorum", hostName);
            try
            {
                HTable tbl = new HTable(hbaseConfig, tableName);
                Scan scan = new Scan();
                ResultScanner scanner = tbl.getScanner(scan);
                Result r;
                while (((r = scanner.next()) != null) && ndx++ < numRecords)
                {
                    List aList = r.list();
                    ListIterator li = aList.listIterator();
                    while (li.hasNext())
                    {
                        kv = (KeyValue)li.next();
                        familyName = Bytes.toString(kv.getFamily());
                        cellName = Bytes.toString(kv.getQualifier());
                        value = Bytes.toString(kv.getValue());
                        // make a unique list of all the column names
                        if (!names.Contains(familyName + ":" + cellName))
                        {
                            names.Add(familyName + ":" + cellName);
                        }
                        // add headers
                        if (currentRow == 2)
                        {
                            currentCell = navigator.Cells[1, currentColumn];
                            currentCell.Value2 = cellName;
                        }
                        currentCell = navigator.Cells[currentRow, currentColumn++];
                        currentCell.Value2 = value;
                    }
                    currentRow++;
                    currentColumn = 1;
                }
                scanner.close();
                tbl.close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return names.ToArray<string>();
        }

Generic filtering and frequency user interface

Below is a close-up screenshot of the HBase pane. The interface in the View Table group allows the user to point to a Hadoop implementation, choose a table and the number of records to load into the active worksheet. Once that is done, the user can then define a MapReduce job using the controls in the Map Reduce group.

The user interface allows filtering on any one column. The combo box control labeled Choose filter column contains all the column names in the form family:cell. The text box labeled FilterValue is the filter which elides all rows where the chosen column  doesn’t match the filter value. The combo box labeled Column to Count is used to choose the column whose values will be grouped and counted. The above values ask the question: “What are the pages—specifically the frequencies of the pages— visited by the IP address 80.240.129.183“.

When the button, Map Reduce, is clicked, this handler is invoked:

        private void onMapRedButtonClick(object sender, EventArgs e)
        {
            this.filterColumn = this.filterComboBox.Text;
            this.filterValue = this.filterValueTextBox.Text;
            this.frequencyColumn = this.frequencyComboBox.Text;
            Excel.Worksheet activeWorksheet 
                = ((Excel.Worksheet)Globals.ExcelHBaseAddIn.Application.Worksheets[2]);
            activeWorksheet.Name = "Frequency";
            Excel.Range navigator = activeWorksheet.get_Range("A1");
            // most of the fun stuff happens here
            int numRows = MapReduce.executeMapReduce(navigator
                , this.filterColumn
                , this.filterValue
                , this.frequencyColumn
                , this.hostName.Text
                , this.tableName.Text);
            // autofit the range
            Excel.Range c1 = activeWorksheet.Cells[1, 1];
            Excel.Range c2 = activeWorksheet.Cells[numRows, 2];
            this.cols = activeWorksheet.get_Range(c1, c2); 
            this.cols.EntireColumn.AutoFit();
            // bring the worksheet to the top
            activeWorksheet.Activate();
        }

All the work is done by the method MapReduce.executeMapReduce(), partially shown below. The .NET-to-Java method call, HBaseToLinq.FrequencyMapRed.executeMapRed(), is almost the same Java code used in the previous lab, Building a LINQ provider for HBase MapReduce. The only modifications have been to remove hard-coded column names, instead using the programmatic column names for filtering and frequency counts chosen by the user. The method then scans the results of the MapReduce job stored in the table, summary_user, and loads them into a worksheet, returning the number of records in the results table.

            try
            {
                HBaseToLinq.FrequencyMapRed.executeMapRed(hostName
                    , tableName
                    , frequencyColumn
                    , columnToFilter
                    , filterValue);
            }
            catch(Exception ex)
            {
                throw ex;
            }
            Configuration hbaseConfig = HBaseConfiguration.create();
            hbaseConfig.set("hbase.zookeeper.quorum", hostName);
            try
            {
                string cellName = 
                     frequencyColumn.Substring(frequencyColumn.IndexOf(":") +1);
                string familyName = 
                     frequencyColumn.Substring(0, frequencyColumn.IndexOf(":"));
                HTable tbl = new HTable(hbaseConfig, "summary_user");
                Scan scan = new Scan();
                ResultScanner scanner = tbl.getScanner(scan);
                Result r;
                while ((r = scanner.next()) != null)
                {
                    rowKey = Bytes.toString(r.getRow());
                    count = Bytes.toInt(r.getValue(Bytes.toBytes(familyName)
                         , Bytes.toBytes("total")));
                    currentCell = navigator.Cells[currentRow, currentColumn++];
                    currentCell.Value2 = rowKey;
                    currentCell = navigator.Cells[currentRow++, currentColumn];
                    currentCell.Value2 = count;
                    currentColumn = 1;
                }
                scanner.close();
                tbl.close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return currentRow - 1;

Here’s a screen shot of the Excel add-in after performing the MapReduce.

Visualizing data

Data visualization through graphs and charts is an important final step when investigating and analyzing data. Clicking on the button Chart Frequencies causes the add-in to create a stacked column chart of the Frequency worksheet. Here’s the code for the handler, onChartFrequenciesClick().

        private void onChartFrequenciesClick(object sender, EventArgs e)
        {
            Excel.Workbook wb = Globals.ExcelHBaseAddIn.Application.ActiveWorkbook;
            Excel.Chart chart = (Excel.Chart)wb.Charts.Add();
            chart.ChartType = Excel.XlChartType.xlColumnStacked;
            chart.SetSourceData(this.cols, Excel.XlRowCol.xlColumns);
            chart.HasTitle = true;
            string filterName = this.filterColumn.Substring(this.filterColumn.IndexOf(":") + 1);
            string frequencyName 
                 = this.frequencyColumn.Substring(this.frequencyColumn.IndexOf(":") + 1);
            chart.ChartTitle.Text = "Frequency of " 
                  + frequencyName 
                  + " when " + filterName 
                  + " = " + this.filterValue;
        }

This screen shot of the add-in shows the resulting chart. Notice that the MapReduce columns for filtering and frequency are different than the previous example. Here, the question being asked is “What is the frequency of visiting IP addresses for the page, /cats.html”.

Conclusion

Building an Excel add-in that supports viewing any HBase table of column families and provides filtering and Map Reduce frequency counts is relatively straightforward. Leveraging the HBase Java client APIs using JNBridgePro to create .NET proxies is key to the simplicity.  By keeping the MapReduce job both on the Java side and generic, any table can be filtered and reduced to frequencies of one particular column.

The source for this example can be downloaded here.

Java/Excel (and other Office) Interoperability

Update January 2018
We’re curious: this blog post has consistently been a top performer for many years now, and we’d like to know why.

  • This field is for validation purposes and should be left unchanged.

I’ve been working with a customer on a project to show how Java code can access Microsoft Excel files through the .NET interfaces provided in Visual Studio Tools for Office (VSTO). Although the example uses Excel, the principles here will work with the VSTO interfaces for any other Office application, including Word, Outlook, and PowerPoint.

The customer is using .NET Framework 1.1, so we concentrated on VSTO 2003 and the 1.x-targeted version of JNBridgePro 3.0, but the same ideas will work with .NET 2.0 and VSTO 2005.

We started with the following C# program:

using System;
using Excel = Microsoft.Office.Interop.Excel;
namespace ExcelProject
{
   public class ExcelInterop
   {
      static void sheetChangeHandler(object ah, Excel.Range target)
      {
         Console.WriteLine(“the worksheet changed!”);
         Console.WriteLine(“target = [" + target.Row + ", " +
         target.Column + "]“);
      }

      public static void Main(string[] args)
      {
         Excel.WorkbookEvents_SheetChangeEventHandler
            sheetChangeEvent;
         Excel.Application theApp = new Excel.Application();
         Excel.Workbooks wbs = theApp.Workbooks;
         Excel.Workbook wb =
            wbs.Open(@”C:\Data\JNBridge\ExcelProject\Book1.xls”,
            Type.Missing, Type.Missing, Type.Missing, Type.Missing,
            Type.Missing, Type.Missing, Type.Missing,
            Type.Missing, Type.Missing, Type.Missing, Type.Missing,
            Type.Missing, Type.Missing, Type.Missing);
         sheetChangeEvent =
            new Excel.WorkbookEvents_SheetChangeEventHandler(
               sheetChangeHandler);
         wb.SheetChange += sheetChangeEvent;
         Excel.Worksheet ws = (Excel.Worksheet)wb.Sheets[1];
         ((Excel.Range)ws.Cells[1,1]).Value2 = “hello world”;
         wb.Save();
         wb.Close(false, Type.Missing, Type.Missing);
         theApp.Quit();
      }
   }
}

The program takes an existing Excel file Book1.xls, opens it, assigns the string “hello world” to cell A1, then saves it away and quits. We wanted to write a Java program that would do the same thing.

As a first pass, we’ll start by generating proxies for all the VSTO classes used above: Application, Workbooks, Workbook, Worksheet, Range, WorkbookEvents_SheetChange, and Type (plus supporting classes). However, if you just do a straightforward one-to-one translation from C# to Java, you’ll run into a couple of issues.

The first thing you’ll discover is that Application is an interface and you can’t instantiate it:

Application theApp = new Application();

So how come we can instantiate Application in C#? It turns out that the Application interface is annotated with a CoClassAttribute that points to Excel.ApplicationClass. The C# compiler has hooks that understand CoClassAttribute, and the compiled code actually instantiates ApplicationClass, which implements the Application interface This metadata isn’t proxied to the Java side, and Java wouldn’t know what to do with it anyway. The solution is to proxy Excel.ApplicationClass and instantiate that in Java:

Application theApp = new ApplicationClass();

The Excel objects that are returned from the API are actually instances of System.__ComObject, which is a special .NET wrapper for the underlying COM objects returned by the various Office APIs. Now, System.__ComObjects don’t actually implement the interfaces they stand in for (try applying the reflection method Type.GetInterfaces() to a System.__ComObject to verify this), although if a System.__ComObject o is supposed to represent an object with an interface I, the test “o is I” will succeed. In any case, the built-in .NET-COM interop seems to take care of this, but the metadata needed to make this happen isn’t proxied across to the Java side and this leads to ClassCastExceptions when the proxied System.__ComObjects are assigned to variables of interface type.

To address this issue, we need to make sure that all interop with COM objects happens on the .NET side. To do this, we create wrapper classes for each of the Excel classes we use. The wrappers are what gets proxied, and the underlying wrapper objects are simply pass-throughs to the COM objects — since the calls to the COM objects are done on the .NET side, the interop automatically happens.

We create wrapper classes for each interface that we used. As an example of how this can be done, consider the wrapper that we write for the Excel.Workbook interface:

using Excel = Microsoft.Office.Interop.Excel;
namespace com.jnbridge.office.interop.excel
{
   public class WorkbookImpl: Excel.Workbook
   {
      private Excel.Workbook wb;      

      public WorkbookImpl(Excel.Workbook theWb)
      {
         this.wb = theWb;
      }

      public void Dummy16()
      {
         wb.Dummy16();
      }

      public bool IsInplace
      {
         get
         {
            return wb.IsInplace;
         }
      }

      public Microsoft.Office.Interop.Excel.Sheets Sheets
      {
         get
         {
            return new SheetsImpl(wb.Sheets);
         }
      }
      // etc
   }
}

Note that the wrapper methods are passthroughs. Also look at the last property, Sheets: it returns an object that implements the Sheets interface. Since that object will itself be a System.__ComObject, we need to wrap it with the wrapper for Sheets (SheetsImpl) that we’ve written before it can be returned. In general, we need to do this with all members that return an object of an Office API type, since they will all be System.__ComObjects. Objects of other types, including primitives and strings, don’t have to be wrapped.

Some of the Excel API methods take arguments of Type.Missing to indicate that the value isn’t being supplied. Unfortunately, the .NET reflection API’s dynamic dispatch mechanism gets confused when it sees Type.Missing, since it’s used as a special indicator in vararg calls. Since JNBridgePro uses this reflection mechanism, it gets confused, too. To address this problem, we have the Java side pass null for those arguments that aren’t used, and the wrapper will translate them to Type.Missing before calling the COM object, as in this wrapper call to WorkbookImpl.Close():

public void Close(object SaveChanges, object Filename, object RouteWorkbook)
{
   wb.Close(SaveChanges,
      Filename != null ? Filename : Type.Missing,
      RouteWorkbook != null ? RouteWorkbook : Type.Missing);
}

Finally, we need to address Java-side event handlers. When an event is fired, if an Excel object is being passed to the event handler, what really gets passed is a COM object — so we need to make sure these COM object arguments are wrapped before they get to the Java side. The way to do this is to wrap the event handler so that it intervenes in the event call and wraps the COM objects before passing them to the Java side. Here’s how we did SheetChange Event in the WorkbookImpl class:

// this is a small nested class inside WorkbookImpl
// use this wrapper so that the call to the ComObject is done on the
// .NET side, and the Java side always sees the WorkbookImpl and
// RangeImpl objects
private class SheetChangeEventHandlerWrapper
{
   Excel.WorkbookEvents_SheetChangeEventHandler sceh;

   public SheetChangeEventHandlerWrapper(
      Excel.WorkbookEvents_SheetChangeEventHandler theSceh)
   {
      this.sceh = theSceh;   // this is the Java side handler
   }

   public void wrapped_sceh(object sender, Excel.Range target)
   {
      // call the Java side handler with the RangeImpl object
      sceh(new WorksheetImpl((Excel.Worksheet) sender),
      new RangeImpl(target));
   }
}

public event Microsoft.Office.Interop.Excel.WorkbookEvents_SheetChangeEventHandlerSheetChange
{
   add
   {
      // create a new SheetChangeEventHandler
      // that will call this one
      // value is the Java-side handler
      SheetChangeEventHandlerWrapper mySceh
         = new SheetChangeEventHandlerWrapper(value);
      // add the new one to SheetChangeEventHandler
      // – the handler we register is the wrapper handler
      wb.SheetChange +=
         new Excel.WorkbookEvents_SheetChangeEventHandler(
            mySceh.wrapped_sceh);
   }
   remove
   {
      // create a new SheetChangeEventHandler
      // that will call this one
      // value is the Java-side handler
      SheetChangeEventHandlerWrapper mySceh
         = new SheetChangeEventHandlerWrapper(value);
      // remove the new one to SheetChangeEventHandler
      // – the handler we register is the wrapper handler
      wb.SheetChange -=
         new Excel.WorkbookEvents_SheetChangeEventHandler(
            mySceh.wrapped_sceh);
   }
}

That should give you enough information to implement your own wrapper classes. If you only implement wrapper classes for the VSTO classes you need, it should be pretty straightforward. If you want to implement wrappers for the entire VSTO API, you might want to contact us. 🙂

Now generate the proxy jar file as follows:

Start with a new Java-to-.NET project. Add the VSTO Excel assembly (from the GAC) and the new wrapper DLL to the assembly list. Load the following classes (using “Add classes from assembly list…”) from the primary interop assemblies (plus all supporting classes):

  • Microsoft.Office.Interop.Excel.Application
  • Microsoft.Office.Interop.Excel.Workbooks
  • Microsoft.Office.Interop.Excel.Workbook
  • Microsoft.Office.Interop.Excel.Sheets
  • Microsoft.Office.Interop.Excel.Worksheet
  • Microsoft.Office.Interop.Excel.Range

Load all the classes (using “Add classes from assembly file…”) from the wrapper DLL.

Generate proxies for all the classes that you’ve loaded.

Make sure you’ve got the following on your machine:

  • NET Framework 1.1
  • VSTO 2003
  • JNBridgePro 3.0
  • place the 1.x-targeted versions of jnbshare.dll, jnbsharedmem.dll, and jnbjavaentry2.dll in the GAC.

Place the following code in a file MainClass.java so that the paths to jnbjavaentry.dll and ExcelWrappers.dll are correct on your machine, and compile it. Make sure that jnbcore.jar, bcel-5.1-jnbridge.jar, and the jar file with the proxies for the Excel VSTO interfaces and the wrapper classes are in the compilation classpath. Note that we’re configuring JNBridgePro programmatically so you won’t need to worry about a configuration file. It’s set up for shared memory.

Note the long strings below have been wrapped. Make sure they’re correct when you try to compile this code.

import Microsoft.Office.Interop.Excel.*;
import System.Type;
import System.BoxedBoolean;
import System.DotNetString;
import System.BoxedInt;
import System.Object;
import com.jnbridge.jnbcore.DotNetSide;
import java.util.Properties;
import com.jnbridge.office.interop.excel.*;
public class MainClass
{
   public static class SheetChangedEventHandler
      implements WorkbookEvents_SheetChangeEventHandler
   {
      public void Invoke(Object sender, Range target)
      {
         System.out.println(“the worksheet changed!”);
         System.out.println(“target = [" + target.Get_Row() + ", " +
            target.Get_Column() + "]“);
      }
   }

   public static void main(String[] args) 
   {
      // set up the properties
      Properties props = new Properties();
      props.put(“dotNetSide.serverType”, “sharedmem”);
      props.put(“dotNetSide.assemblyList.1″,
         “C:\\Data\\JNBridge\\ExcelWrappers\\bin\\Debug\\ExcelWrappers.dll”);
      props.put(“dotNetSide.assemblyList.2″,
         “Microsoft.Office.Interop.Excel,
         Version=11.0.0.0,Culture=neutral, 
         PublicKeyToken=71e9bce111e9429c”);
      props.put(“dotNetSide.assemblyList.3″,
         “office, Version=11.0.0.0, Culture=neutral,
         PublicKeyToken=71e9bce111e9429c”);
      props.put(“dotNetSide.assemblyList.4″,
         “Microsoft.Vbe.Interop.Forms, Version=11.0.0.0, Culture=neutral,
         PublicKeyToken=71e9bce111e9429c”);
      props.put(“dotNetSide.assemblyList.5″,
         “System, Version=1.0.5000.0, Culture=neutral,  
         PublicKeyToken=b77a5c561934e089″);
      props.put(“dotNetSide.assemblyList.6″,
         “System.Data, Version=1.0.5000.0, Culture=neutral,
         PublicKeyToken=b77a5c561934e089″);
      props.put(“dotNetSide.assemblyList.7″,
         “System.Windows.Forms, Version=1.0.5000.0, Culture=neutral,
         PublicKeyToken=b77a5c561934e089″);
      props.put(“dotNetSide.assemblyList.8″,
         “System.XML, Version=1.0.5000.0, Culture=neutral, 
         PublicKeyToken=b77a5c561934e089″);
      props.put(“dotNetSide.assemblyList.9″,
         “Microsoft.Vbe.Interop, Version=11.0.0.0, Culture=neutral, 
         PublicKeyToken=71e9bce111e9429c”);
      props.put(“dotNetSide.javaEntry”,
         “C:/Program Files/JNBridge/JNBridgePro v3.0/1.x-targeted/JNBJavaEntry.dll”);
      DotNetSide.init(props);
      Application theApp =
         new com.jnbridge.office.interop.excel.ApplicationClass(
         new Microsoft.Office.Interop.Excel.ApplicationClass());
      Workbooks wbs = theApp.Get_Workbooks();
      Microsoft.Office.Interop.Excel.Workbook wb =
         wbs.Open(“C:\\Data\\JNBridge\\ExcelProject2\\Book1.xls”,
         null, null, null, null, null, null, null,
         null, null, null, null, null, null, null);
         // use null instead of Type.Missing
      WorkbookEvents_SheetChangeEventHandler sceh =
         new SheetChangedEventHandler();
      wb.add_SheetChange(sceh);
      Microsoft.Office.Interop.Excel.Sheets wss
         = (Microsoft.Office.Interop.Excel.Sheets)wb.Get_Sheets();
      Microsoft.Office.Interop.Excel.Worksheet ws =
         (Worksheet)wss.Get_Item(new BoxedInt(1));
      Range r = ws.Get_Cells();
         Range r2 = (Range)r.Get_Item(new BoxedInt(1), new BoxedInt(1));
      r2.Set_Value2(new DotNetString(“hello world”));
      wb.Save();
      wb.Close(new BoxedBoolean(false), null, null);
      theApp.Quit();
   }
}

Note how we only explicitly refer a wrapper proxy once, when we’re wrapping the VSTO ApplicationClass. In all other cases, we continue to use the VSTO interfaces, but if we’ve constructed the wrapper classes correctly, wrappers will be returned for each call and we address them through the interfaces, so they’re invisible.

To run this application, just make sure that jnbcore.jar, bcel-5.1-jnbridge.jar, and the proxy jar file are in your classpath, and run

java MainClass

If you’ve done this properly, the Excel spreadsheet will now have “hello world” in cell A1.

Once you get past the need for wrappers, setting up this interop is pretty simple, and it’ll work for all the Office APIs, not just Excel.

A question for all of you out there.  Would you be interested in having JNBridge supply an assembly with wrappers for the Office APIs, or are you happy to do it yourselves?  Let me know.