Creating Marshaler Extensions

About this chapter

This chapter describes how to create marshaler extensions.

About marshaler extensions

Marshaler extensions can act as bridges between PowerBuilder and other components, such as EJB components, Java classes, and Web services, as long as those components can be called from C++.

To create a marshaler extension, build a PBX that contains at least one class that implements the IPBX_Marshaler interface, as well as one or more native classes. The extension must contain code that associates the marshaler with a proxy for the component you want to call.

If you build a marshaler extension, you should also provide a tool that generates proxies so the components can be called from PowerBuilder. For example, PowerBuilder provides a marshaler extension for calling EJB components from PowerBuilder, and it provides a tool for generating proxies for EJB components.

This chapter provides an overview based on the Java Marshaler sample application, and shows some extracts from the sample.

This chapter describes the major tasks involved in:

This chapter does not show detailed code samples, and the fragments shown simplify the coding involved. For a more complete understanding of the process of building a marshaler extension, download the sample available on the website.

Developing the PowerBuilder extension

A PowerBuilder marshaler extension usually provides a native class that acts as a creator. This class defines a function that creates an instance of the foreign component that is specified in the parameters passed into the function (1). If it succeeds in creating an instance of the foreign component (2), it creates a proxy for it using the PBVM (3, 4), creates a marshaler object (5), and associates the marshaler with the proxy (6).

Figure: Creating a foreign component, proxy, and marshaler

When a function of the proxy object is called in PowerScript, the PBVM calls the InvokeRemoteMethod function on the marshaler object through the proxy (1, 2). The marshaler object translates PowerBuilder function calls into requests that the foreign component understands, sends the requests (3), waits for a response, and send the results back to PowerBuilder (4).

Figure: Invoking a remote method

To develop the extension, you need to:

Step 1: Describe the extension

Step 2: Implement the creator class

Step 3: Implement the marshaler class

Step 1: Describe the extension

The class that implements the creator, called CJavaVM in the following example, must export the PBX_GetDescription function. It inherits from NonVisualObject and has two functions, CreateJavaObject and CreateJavaVM:

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription()
{
  static const TCHAR desc[] = {
    "class javavm from nonvisualobject\n" 
    "function long createjavavm(string classpath,
       string properties[])\n" 
    "function long createjavaobject(ref powerobject
       proxyobject, readonly string javaclassname,
       readonly string proxyname)\n" 
    "end class\n" 
  };
  return desc;
}

Step 2: Implement the creator class

Like any nonvisual native class, the CJavaVM class must implement the Invoke and Destroy functions in addition to the class functions CreateJavaObject and CreateJavaVM.

The CreateJavaVm function of CjavaVM gets the classpath and properties from the PBCallInfo structure. Then it loads the Java VM by calling the loadJavaVM function of a wrapper class called JavaVMWrapper. The JavaVMWrapper class encapsulates the JavaVM interface provided by JNI.

The CreateJavaObject function creates an instance of a Java class based on the given class name, creates a PowerBuilder proxy object for the Java object, creates a JavaMarshaler object, and associates the marshaler object with the proxy object.

The following is the CreateJavaObject function:

PBXRESULT CJavaVM::CreateJavaObject  (
  IPB_Session  *session, 
  pbobject     obj, 
  PBCallInfo   *ci  )
{
  enum  {
    kSuccessful = 0,
    kInvalidJavaClassName = -1,
    kFailedToCreateJavaClass = -2,
    kInvalidProxyName = -3,
    kFailToCreateProxy = -4  };

  // Get java class name.
  string jclassName;

  {
    pbstring jcn = ci->pArgs->GetAt(1)->GetPBString();
    if (jcn == NULL)
    {
      ci->returnValue->SetLong(kInvalidJavaClassName);
      return PBX_OK;
    }
    
    else    {
      jclassName = session->GetString(jcn);
    }
  }

  // Create java object
  JavaVMWrapper*  jvm = JavaVMWrapper::instance();
  JNIEnv*         env = jvm->getEnv();

  jclass jcls = env->FindClass(jclassName.c_str());
  jobject jobj = NULL;

  if (jcls != NULL)
  {
    JLocalRef  lrClz(env, jcls);

    jmethodID mid = env->GetMethodID(jcls, "<init>",
      "()V");
    if (mid != NULL)
    {
      jobj = env->NewObject(jcls, mid);
    }
  }

  // Get PowerBuilder proxy name
  string proxyName;

  {
    pbstring pn = ci->pArgs->GetAt(2)->GetPBString();

    if (pn == NULL)
    {
      ci->returnValue->SetLong(kInvalidProxyName);
      return PBX_OK;
    }
    else
    {
      proxyName = session->GetString(pn);
    }
  }
  // Find proxy class
  pbgroup group = session->FindGroup(proxyName.c_str(),
    pbgroup_proxy);
  if (group == NULL)
  {
    ci->returnValue->SetLong(kInvalidProxyName);
    return PBX_OK;
  }

  pbclass cls = session->FindClass(group, proxyName.c_str());
  if (cls == NULL)
  {
    ci->returnValue->SetLong(kInvalidProxyName);
    return PBX_OK;
  }

  // Create PowerBuilder proxy object.
  pbproxyObject proxy = session->NewProxyObject(cls);
  if (proxy == NULL)
  {
    ci->returnValue->SetLong(kFailToCreateProxy);
    return PBX_OK;
  }

  // Create JavaMarshaler
  JavaMarshaler* marshaler = new JavaMarshaler(env,
    proxy, jobj);

  // Associate the JavaMarshaler with the proxy
  session->SetMarshaler(proxy, marshaler);
    ci->pArgs->GetAt(0)->SetObject(proxy);

  ci->returnValue->SetLong(kSuccessful);
    return PBX_OK;
}

Step 3: Implement the marshaler class

The marshaler class must implement the InvokeRemoteMethod function. It also needs to provide a Destroy function and get the handle of the module. This is the declaration:

#include <jni.h>
#include <pbext.h>

class JavaMarshaler : public IPBX_Marshaler
{
  jobject      d_jobject;
  pbproxyObject  d_pbobject;

public:
  JavaMarshaler(JNIEnv* env, pbproxyObject pbobj, jobject ejbobj);
  ~JavaMarshaler();

  virtual PBXRESULT InvokeRemoteMethod    (
    IPB_Session*  session,
    pbproxyObject  obj,
    LPCSTR      method_name,
    PBCallInfo*    ci    );

  virtual pbulong  GetModuleHandle();

  virtual void Destroy();
};

The InvokeRemoteMethod function calls Java functions through JNI. This is the implementation in JavaMarshaler.cpp:

#include "JavaMarshaler.h"
#include "JMethod.h"
#include "JavaVMWrapper.h"

extern pbulong g_dll_hModule;

pbulong  JavaMarshaler::GetModuleHandle()
{
  return g_dll_hModule;
}

//****************************************************
//  JavaMarshaler
//****************************************************
JavaMarshaler::JavaMarshaler
(
  JNIEnv*      env, 
  pbproxyObject  pbobj,
  jobject      ejbobj
)
:  d_jobject(env->NewGlobalRef(ejbobj)),
     d_pbobject(pbobj)
{
}

JavaMarshaler::~JavaMarshaler()
{
  JNIEnv* env = JavaVMWrapper::instance()->getEnv();

  if (d_jobject != NULL && env != NULL)
    env->DeleteGlobalRef(d_jobject);
}

PBXRESULT JavaMarshaler::InvokeRemoteMethod
(
  IPB_Session*   session,
  pbproxyObject  obj,
  LPCSTR         szMethodDesc,
  PBCallInfo*    ci
)
{
  static char* eFailedToInvokeJavaMethod  = 
    "Failed to invoke the Java method.";

  JNIEnv* env = JavaVMWrapper::instance()->getEnv();
  JMethod method(this, szMethodDesc);

  try  {
    if (d_jobject != NULL)
    {
      method.invoke(session, env, d_jobject, ci);
      if (env->ExceptionCheck() == JNI_TRUE)
      {
        string error(eFailedToInvokeJavaMethod);
        error += "\n";
        // Throw exception here

        return PBX_E_INVALID_ARGUMENT;
      }
    }
  }
  catch(...)
  {
  }

  return PBX_OK;
}

void JavaMarshaler::Destroy()
{
  delete this;
}

Generating proxies for Java classes

You need to develop PowerBuilder proxies for the Java classes you want to invoke from PowerBuilder. You can develop proxies using Java reflection, from Java source code directly, or using the javap tool. For example, suppose you want to invoke this Java class:

public class Converter
{
public double dollarToYen(double dollar);
public double yenToEuro(double yen);
}

The PowerBuilder proxy for this Java class could be stored in a file called converter.srx that looks like this:

$PBExportHeader$converter.srx
$PBExportComments$Proxy generated for Java class

global type Converter from nonvisualobject
end type
global Converter Converter

forward prototypes
  public:
function  double dollarToYen(double ad_1) alias 
   for "dollarToYen,(D)D"
function  double yenToEuro(double ad_1) alias
   for "yenToEuro,(D)D"
end prototypes

Notice that both PowerBuilder proxy methods have an alias containing the Java method name and method signature. This is necessary because Java is case sensitive, but PowerBuilder is not. The extension uses the alias information is used by the extension to find the corresponding Java methods.

To add the proxy to a PowerScript target, select the library where the proxy will be stored in the System Tree, select Import from the pop-up menu, and browse to select converter.srx.

Calling the Java class from PowerBuilder

In the open event of a window, create a Java VM:

// instance variable: javavm i_jvm
string properties[]
i_jvm = create javavm
string classpath
i_jvm.createjavavm(classpath, properties)

In the clicked event of a button on the window, create an instance of the Converter class using the CreateJavaObject method of the JavaVM instance, and call the conv method on the Converter class:

converter conv
double yen
i_jvm.createjavaobject(conv, "Converter", "converter")
yen = conv.dollarToYen(100.0)
messagebox("Yen", string(yen))

When the CreateJavaObject method is called in PowerScript, the PBVM calls the corresponding C++ method in the extension. The C++ method creates a Java Converter object through JNI. If it is successful, the method creates an instance of the PowerBuilder Converter proxy and a JavaMarshaler object, and associates the JavaMarshaler object with the PowerBuilder proxy.

When conv.dollarToYen(100.0) is called in PowerScript, the PowerBuilder VM calls the InvokeRemoteMethod method on the JavaMarshaler object, which delegates the call to the Java Converter object though JNI and returns the result to PowerBuilder.