Saturday, September 03, 2005

The lookup code generated for the proxy class is based on the strategy outlined in the previous post. First, some code snippets, followed by a discussion:


class HelloClass;
class HelloClassProxy;

typedef JSBool(HelloClassProxy::*JSSIG_HelloClass)(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);

class HelloClassProxy {
public:
HelloClassProxy();
~HelloClassProxy();
JSSIG_HelloClass
GetEntryPoint(const string& name);
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;
};

extern "C" HelloClassProxy *NewHelloClassProxy();
extern "C" void DeleteHelloClassProxy(HelloClassProxy *ptr);
extern "C" JSSIG_HelloClass HelloClassProxyLookup(HelloClassProxy *ptr, const string& name);


The typedef JSSIG_HelloClass represents a pointer to a HelloClassProxy member function with the prototype mandated by the mozilla JavaScript engine. For each class in the component, a similar typedef is generated by the layout engine.

GetEntryPoint() is a new member function that returns a pointer to a HelloClassProxy member function that matches the name passed in. Here is the code that is now being generated for that particular function by iterating the DOM for functions and properties implemented by HelloClassProxy:


JSSIG_HelloClass
HelloClassProxy::GetEntryPoint(const string& name)
{
if (name == "Hello")
return &HelloClassProxy::Hello;
if (name == "Get_age")
return &HelloClassProxy::Get_age;
if (name == "Set_age")
return &HelloClassProxy::Set_age;
return NULL;
}


As you can see, all this function does is perform a simple check for a matching string, and return a pointer to a member function if a match is found. Notice the interesting way by which a pointer to a member function is obtained; this takes some getting used to if you and an old-timer C programmer (like I was). Yes, I could have put the names and functions into an STL map, but this code was more straightforward to implement in the layout code generator.

The final function generated by the layout engine to support lookups is a simple extern "C" function that allows the layout engine to call into the component and invoke GetEntryPoint() thru the class object that it holds:


extern "C" JSSIG_HelloClass
HelloClassProxyLookup(HelloClassProxy *ptr, const string& name)
{
if (!ptr)
return NULL;
return ptr->GetEntryPoint(name);
}


That's pretty much it. This should be all that is needed for the layout engine to populate the JavaScript object with entry points into the proxy classes. The next step is to add code to the layout engine that does just that.

Creating the public functions to allocate and deallocate proxy objects was straightforward enough; simply generate into the proxy header and source files code like the following:


extern "C" HelloClassProxy *
NewHelloClassProxy()
{
HelloClassProxy *ptr = new HelloClassProxy();
return ptr;
}

extern "C" void
DeleteHelloClassProxy(HelloClassProxy *ptr)
{
if (ptr)
delete ptr;
}

HelloClassProxy::HelloClassProxy()
{
m_impl = new HelloClass();
}

HelloClassProxy::~HelloClassProxy()
{
if (m_impl)
delete m_impl;
}


At runtime, the layout engine, when it goes to create the JavaScript object that represents an instance of the proxy class in response to a query by the user JavaScript code to obtain an instance of an object, constructs a string that consists of a concatenation of the strings "New", the class name (e.g., HelloClass), and "Proxy". It then calls the Library class object to query the shared library for a function with that name (in this case, NewHelloClassProxy). If it is returned a non-NULL value, it then can call that function to allocate a proxy object. Notice that allocating the proxy object also causes the concrete object (HelloClass) wrapped by the proxy to be instantiated (see the source for the HelloClassProxy constructor, above). Here's a snippet of that code taken from the layout engine:


PRStatus
Object::CreateJSObject()
{
PRStatus ret = PR_SUCCESS;
JSEngine *js = JSEngine::GetJSEngine();

if (!js)
return PR_FAILURE;

Component *comp = GetComponent();

if (!comp)
return PR_FAILURE;

Library *lib = comp->GetLibrary();

if (!lib)
return PR_FAILURE;

JSContext *ctx = js->GetContext();
JSObject *obj = js->GetGlobalObject();

string name = GetAttributeByName(string("name"))->GetValue();

// create an instance of the proxy class

string creator;

creator = "New" + name + "Proxy";

void *fptr = lib->FindSymbol(creator);


However, we have a problem, Houston. Once the layout engine has an instance of the proxy object (in fptr, above), it needs to create entry points in the JavaScript object for each method implemented by the proxy object. These entry points are what the JavaScript engine uses to map calls made by the user's JavaScript into C/C++ calls. For the HelloClassProxy class, this includes the methods Hello(), Get_age(), and Set_age(). With an instance of the object in hand, you say, the layout code can obtain the addresses of these objects, so what is the problem? The problem is that the best we can do (in my current design that is) is obtain effectively a void * when calling the shared library implementation of NewHelloClassProxy(). The layout engine cannot allocate a pointer to a HelloClassProxy object; to do otherwise would require the layout engine to anticipate the types of all proxy objects it is ever going to be expected to interface to. But with only a void *, I can't possible obtain addresses of functions in the object's vtable.

But there is a solution. We can generate into the proxy source an addition extern "C" interface, perhaps call it HelloClassProxyLookup(), that takes a string and a void * representing the proxy object, and then returns a pointer to the method in the object that pertains to that name. This pointer can then be stuffed into the JavaScript object. The code in the layout engine that generates the proxy source (and the lookup function) will be modified generate the code needed to support this functionality.

Another thing I need to concern myself with is passing "this" to the proxy functions from JavaScript. Currently, I am not accounting for this required argument.