Developing Plugin Models for DOME

Introduction

This guide explains how to write plugin models for DOME using the plugin API. The plugin API encapsulates JNI (Java Native Interface) calls to the native code so that the plugin writer is not required to know JNI.

The architecture section of the guide explains what pieces of code are to be reused and what pieces need to be implemented by the plugin writer. The section, writing plugin code, gives a step-by-step procedure for plugin model code development and the last section in the document enlists a few possible pitfalls to be avoided during development.

Architecture

../../../_images/dome-plugin-architecture.png

Note

PW indicates the parts of the code to be implemented by the plugin writer.

The above architecture block diagram is self-explanatory.

The plugin writer needs to implement the plugin model and the plugin data types in Java. The plugin model should implement the plugin interface and the plugin data types should implement the corresponding DOME data type interfaces. For example, a PluginReal class should implement the DomeReal interface and so on. Please see the Example Plugin code that accompanying the document for this purpose.

The plugin model and data types call methods in the Java side NativeCaller class, which will seamlessly call the native methods. The plugin writer should include the provided NativeCaller class’s Java and C++ side implementation in their source code tree for the above-mentioned seamless native method calls.

On the C++ or the native side, the plugin writer is expected to write all the classes of her plugin model code, the function pointers to functions in this plugin code and classes encapsulating function arguments. She also needs to implement the NativeWorker classes’ methods.

Writing plugin code

The code snippets mentioned in this section are from the Example plugin code, which shows plugin writers how to write plugin models for DOME.

Following is recommended sequence of steps for the development of the DOME plugin models.

Step 1

Write the native code for the plugin model. It is recommended to write the native model code and test it thoroughly. In the Example plugin, ExampleModel.cpp, ExampleData.cpp files contain the native model and data types.

Step 2

Write the function pointers. Once the native model and data types in step 1 have been finalized, the plugin writer should write the function pointers for the native model and data types methods, which will be later called from Java. In the Example plugin code, file TFunctor.h contains the definition of function pointers. The base class TFunctor defines pure virtual (i.e. abstract) methods. Each subclass of TFunctor then provides real implementation for one of the virtual methods. Other method implementations in a particular subclass are dummy and correct program flow will actually never reach inside those methods. Since the subclasses of TFunctor uses template classes, all classes are put together in a header file TFunctor.h.

It is up to the plugin writer whether she wants to use an inheritance scheme like one used in the Example plugin. It is not mandatory to write Functor classes but function pointers are required. Thus the plugin writer is free to provide the function pointers in a different way.

Step3

Write the argument holder classes. Two files, FuncArgument.h and FuncArgument.cpp, define two classes. They are Args and Argument. Plugin writer should include these files in their project without any modification. In order to pass the arguments to the model methods, plugin writers need to write a number of argument holder classes. In the Example plugin the argument holder classes are defined in MethodArgs.h file and are implemented in MethodArgs.cpp file. The argument holder classes written by plugin writer must extend from the Argument class defined in FuncArgument.h and provide implementation for the storeArgument method.

These argument holder classes are used by the NativeCaller to store argument values. NativeCaller handles JNI for the plugin writer and it should be used in the plugin development without any modification.

Step 4

Write NativeWorker method implementations. NativeWorker.h file defines the NativeWorker class. This file should not be modified as NativeCaller uses it. The plugin writer only needs to do the following in NativeWorker.cpp.

  1. Include the Model header at the start of NativeWroker.cpp.

  2. If the plugin writer wishes, she could define method name constants and enumeration constants for argument information classes. To generate an argument information object, use Args class in FuncArgument.h. Please see NativeWorker.cpp in the Example plugin.

  3. Provide implenmentation for intializeArgInfoMap method. The ArgInfo Map is used by NativeCaller to gather information about the method arguments. Hence correct implementation of this method is crucial.

  4. Provide implenmentation for addToArgMap method. In this method implementation, correct argument holder class should be instantiated depending on the function name passed in. This method is used by NativeCaller to create argument holder objects, which will be later used to store argument values.

  5. Provide implementation for intializeFuncMap method. Here the function pointers need to be stored in the funMap data structure.

  6. Finally, the plugin writer needs to provide implementation for the following methods:

    callObjectFunction, callVoidFunction, callBoolFunction, callDoubleFunction and callIntFunction.

The method names are based on the return types. Inside these methods, use the function pointers and argument holder objects to invoke appropriate methods. Note that by the time program flow reaches in these methods, NativeCaller would have already populated the argument holder objects with argument values.
  1. The implementation of the following methods should not be changed as they are crucial to correct operation of NativeCaller:

    NativeWorker();
    ~NativeWorker();
    Args* getArgInfo(const string function);
    bool argInfoMapContainsKey(const string func);
    void storeArgument(const string funcname, int index, void* argument);
    

This completes the native side implementation of the plugin.

Step 5

Write Java side plugin data type classes. These classes should in turn call the native methods using NativeCaller class on the Java side. Please see ExapleBoolean.java, ExampleReal.java in the Example plugin code given. In the example classes provided, each method e.g. getValue, setValue etc. is overloaded. One method operates on underlying Java data whereas other method invokes the actual native method. This way all the data is loaded on Java side only and transferred to native side only when the execute method on the plugin model is called.

Step 6

Write Java side plugin model. This class must implement the Plugin interface provided on the Java side. Each plugin method should call the corresponding native method. The execute method should load all the java data to native side before native side execution and transfer the results back to Java side after execution is complete. Please see ExamplePlugin.java in the Example plugin code for reference.

Step 7

Test the plugin code. It is advisable to test the plugin thoroughly after each step and after all the steps are completed.

Note that the C++ code should be compiled as a DLL (Dynamic Link Library) on Windows platform, as a LIB file on Solaris platform and so on. When this native library file name is passed to the plugin class constructor as an argument, do not include the file extension and preceding dot. Also this file should be included in system’s path environment so that JNI (Java Native Interface) would be able to locate this file. If Java environment cannot find this file, java.lang.UnsatisfiedLinkError exception will be thrown.

Possible Pitfalls

  • Function Pointers: function signatures should exactly match signatures in function pointers otherwise results can be unpredictable. E.g. although “string” STL type can be interchangeably used for char*, it may not work right in case of function pointers.
  • EXCEPTION_ACCESS_VIOLATION error on Java side could be due to Null Pointers or unfilled array elements on the native side.
  • If the native side does not throw Exceptions with explanatory messages and/or print debugging information, troubleshooting the plugin code can become difficult and time consuming process.