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
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; }
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; }
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; }