Tuesday, August 30, 2005

One month has passed, with plenty of coding since the last time I blogged, so I figured it is time for an update on the implementation of the component scheme that was outlined in my last post. To date, I have completed (but not fully debugged) all of the software that generates proxy header and source files from the SIL file, as well as a header file that is used to define the class that is interfaced to by the proxy. I've also fleshed out most of the code that is called by JavaScript to get an instance of a component, and with that instance, locate and create JavaScript objects that the component exports. I also have implemented a class that thinly wraps NSPR's portable dlsym implementation (PRLibrary, etc.).

Here is some JavaScript that illustrates how a callback (again, a button click handler) obtains a component object, and from that component, obtains an object representing a class implemented by the component, and, finally, calls a function implemented by that class using the object:


function Button1Click()
{
var component = componentmgr.getComponent("94981D9E-FC99-11D9-8BDE-F66BAD1E3F3A");
if (component) {
var anobject = component.getObject("2ABDF00B-025E-11DA-BFFE-000A27942344");

if (anobject) {
anobject.Hello("Syd"); // a function that does exist
anobject.Foobar(); // a function that doesn't exist
}
}
}


In the above, componentmgr is a pre-defined JavaScript object that is made available to all SIL-based applications. This object provides JavaScipt code an interface directly to the layout engine's component manager singleton. Each component (and each component class) is uniquely identified by a uuid ($ man 1 uuidgen if you are not sure what a uuid is). Thus, because every component has a unique ID, and every class has a unique ID, unambiguous references to components and objects can be made. This is a simple and effective scheme. Once an object has been located and is available to JavaScript, all that remains for the code to do is make direct calls to the object's public functions (e.g., Hello() and Foobar() in the above example) to perform the work needed.

Some details needs to be finished up before this is all working correctly.
  • Classes in components need to export extern "C" methods that can be used to create and destroy instances, and component objects need to call these functions to create and destroy class instances.
  • I need to determine some method by which objects are destroyed as the JavaScript scope within which an object has been declared/obtained has exited. Not sure what support the mozilla JavaScript engine provides in this regard, but it must be there, and I will find it :-)
Component developers basically need to implement a shared library that contains sources for both the proxy class and the implementation class. My original scheme called for two libraries, one with proxy code, and the other with implementation, but by placing both in the same library, things are much simpler (the proxy doesn't have to dlload/dlsym for implementation code being the major simplification).

Some code would be nice, you say? The first three files (of the four reproduced below) are generated directly from the SIL file.

First, the proxy header (simple_proxy.h):


/* This is generated source code. Do not edit */

#if !defined(__94981D9E_FC99_11D9_8BDE_F66BAD1E3F3A_PROXY_H__)
#define __94981D9E_FC99_11D9_8BDE_F66BAD1E3F3A_PROXY_H__

#include "prtypes.h"
#include "jsapi.h"

class HelloClass;

class HelloClassProxy {
public:
HelloClassProxy();
~HelloClassProxy();
JSBool
Get_age(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool
Set_age(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool
Hello(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
private:
HelloClass *m_impl;
};
#endif


The above contains JSBool functions that can be called directly from JavaScript via the internal JavaScript object that is created when the client calls the component manager's getObject() function. Notice this object has a member variable called m_impl. The type of this member variable is a pointer to the actual C++ class implemented by the component developer (i.e., the function that does all the real work). Hence, m_impl is the bridge between the proxy and the functionality in the object that the JavaScript application is interested in calling. m_impl is initialized when the proxy object is created.

Next up, the proxy source (simple_proxy.cpp). Its' main job is to marshal arguments that are passed by the JavaScript application to the component object, invoke the component object funtion, and then pass back any return values and status to the JavaScript caller.


/* This is generated source code. Do not edit */

#include "simple_proxy.h"
#include "simple.h"
#include "prtypes.h"
#include "jsstr.h"

HelloClassProxy::HelloClassProxy()
{
}

HelloClassProxy::~HelloClassProxy()
{
}

JSBool
HelloClassProxy::Hello(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
if (!m_impl)
return JS_FALSE;
JSString *str_0 = JSVAL_TO_STRING(argv[0]);
string arg_0(JS_GetStringBytes(str_0));
PRInt32 ret;
ret = m_impl->Hello(arg_0);
*rval = INT_TO_JSVAL(ret);
return JS_TRUE;
}

JSBool
HelloClassProxy::Get_age(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
PRInt32 arg_0;
if (!m_impl || m_impl->Get_age(arg_0) != PR_SUCCESS)
return JS_FALSE;
*rval = JSVAL_TO_INT(arg_0);
return JS_TRUE;
}

JSBool
HelloClassProxy::Set_age(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
PRInt32 arg_0 = JSVAL_TO_INT(argv[0]);
if (!m_impl || m_impl->Set_age(arg_0) != PR_SUCCESS)
return JS_FALSE;
return JS_TRUE;
}
#endif


Next, the class header (simple.h). This file, also generated from the SIL file, defines the class that the component programmer must implement:


/* This is generated source code. Do not edit */

#if !defined(__94981D9E_FC99_11D9_8BDE_F66BAD1E3F3A_COMP_H__)
#define __94981D9E_FC99_11D9_8BDE_F66BAD1E3F3A_COMP_H__

#include "prtypes.h"
#include <string>
using namespace std;

class HelloClass {
public:
HelloClass();
~HelloClass();
PRStatus Get_age(PRInt32& val) {val = m_age; return PR_SUCCESS;};
PRStatus Set_age(PRInt32& val) {m_age = val; return PR_SUCCESS;};
PRInt32 Hello(string& Hello);
private:
PRInt32 m_age;
};
#endif


The final source code (simple.cpp), which is not generated, but is rather created by the component developer, implements the HelloClass class that was defined in simple.h (which was generated from the SIL file). Internally, this class is instantiated and called by the HelloClassProxy proxy class. Here is the source, which could be just about anything the developer desires (as long as it adheres to the contract specified by the SIL file):


#include "simple.h"

HelloClass::HelloClass()
{
}

HelloClass::~HelloClass()
{
}

PRInt32
HelloClass::Hello(string& Hello)
{
printf("******Hello from HelloClass C++\n");
return 1;
}

0 Comments:

<< Home