The Mozilla
Organization
Our Mission
Who We Are
Getting Involved
Community
Editorials
What's New
Newsbot
Development
Roadmap
Module Owners
Blue Sky
Projects
Status
Tools
Products
Source Code
Binaries
Documentation
License Terms
Bug Reports
Quality
Search
Feedback


nsISupports Proxies
Doug Turner

Introduction

First and foremost, this is a work in progress.  It is only a reflection of nsISupports Proxies as of this writing.

A "Proxy", in this context, is a stub object which enables a method of any class which is derived from nsISupports to be called on any in-process thread.  "Proxy" itself is possibly not the correct word to use, but this I use it for historical reasons.  nsISupports Proxies is the umbrella name under which the nsProxyEvent class and ProxyObjects live.  ProxyObjects are all objects that uses the nsProxyEvent class and the typelib together to make a generic and dynamic proxy.  This is what most people will be using.  The nsProxyEvent class are usually handwritten and are relatively low level.

The main reason for nsISupports Proxies is that Javascript and UI are on a single thread.  When one is busy, the other is blocked. A great example of this is XPInstall.  Its installation scripts differ from the majority of Javascripts, which are small and can be quickly run.  XPInstall installation scripts are sometime very complex and can require long execution time due to unzipping or native file system actions.   If XPInstall ran on the UI thread, the product would appear frozen until the script was complete.  This is definitely bad.  Because of this, XPInstall was moved to its own thread.  Now XPInstall can do its installations while the product renders, but now XPInstall can not access UI elements such as a progress meter or a confirmation dialog.  How can a separate non-UI thread act as if it was on the UI thread?  Herein lays the utility of nsISupports Proxies.

I believe that other people working on Seamonkey need similar solutions.  In this document, I will try to explain how nsISupports Proxies work.
 

How does it work?

The foundation of nsISupports Proxies is the plevent which is here:

http://lxr.mozilla.org/seamonkey/source/nsprpub/lib/ds/plevent.c
http://lxr.mozilla.org/seamonkey/source/nsprpub/lib/ds/plevent.h

The header file describes in detail exactly how plevents work.  It basically is a process that allows you to post events to a queue and have a handler called when they are removed.  It would benefit you to read this.

Directly above this layer is the nsProxyObject, which Alec Flett designed and developed.  I encapsulated his work into a C++ object, but all praise and many kudos go to him.

Most users of nsISupport Proxies will never have to use nsProxyObject or PLEvents directly.  One might only if a class that does not use the xpidl/typelib. If this case, you will have to create a handwritten proxy.  This is not hard, but there are certain pitfalls that you should be aware of.  More on handwritten proxies later.

From a user point of view, the next layer is the nsIProxyObjectManager. You call GetService to obtain it:
 
nsIProxyObjectManager*  proxyObjectManager;

rv = servMgr->GetService(kProxyObjectManagerCID, 
                         nsIProxyEventManager::GetIID(), 
                         (nsISupports**)& proxyObjectManager);

The only API, which is implemented, is:
 
NS_IMETHOD GetProxyObject( PLEventQueue *destQueue, 
                           REFNSIID aIID,
                           nsISupports* aObj, 
                           void** aProxyObject);

Given an event queue to execute methods on, nsISupports object that has been created and an IID which describes the object, a new nsISupports proxy object will be returned.  (Of course there is room for further development here.  A method, which allocates an object for you, would be a simple example of how we can expand this manager.)

Once you have a proxy object, you may use it as if it is the "real" object.  All the methods that are in the "real" object are stubbed into the proxy object.  Here is a snippet of code which creates a proxy object and uses it:
 
 
nsIProxyObjectManager*  proxyObjectManager;

nsComponentManager::CreateInstance(kProxyObjectManagerCID,
                                   nsnull,
                                   nsIProxyObjectManager::GetIID(),
                                   (void**)&proxyObjectManager);

if (proxyObjectManager == nsnull) return;  // handle error here

nsITestXPCFoo         *proxyObject;               // This is just an interface pointer
nsTestXPCFoo          *foo  = new nsTestXPCFoo(); // This is the class

if (foo == nsnull) return; // handle error here

proxyObjectManager->GetProxyObject( argsStruct->queue, // This is somehow obtained* 
                                    nsITestXPCFoo::GetIID(), 
                                    foo,
                                    (void**)&proxyObject);

int a;
nsresult rv = proxyObject->Test(threadNumber, 0, &a);

When you are finished with a proxy object, you can call NS_RELEASE on it.  It will take care of freeing the "real" object as well as itself.

A point here to bring up is how do we supply the event queue to GetProxyObject?  In the above example, I passed the struct argsStruct to this function.  This is probably not the most graceful way of doing it.  Since a common use of proxies may be to get or control UI, it would be very nice to have something in the nsIEventQueueService possibly which will return the UI thread.  In this manner, we could do away with passing an event queue pointer around.  (Is there a better way to do this??)

Getting back to the example, when you make a call on a nsISupports Proxy, with the magic of typelib and xptcall, your parameters and method information are marshaled across the thread via PLEvents.  Once it is posted to the other thread, the caller (i.e. the user of nsISupports Proxy) will block.  The receiving event queue will process the event and the event handler will invoke your method.

Yes, it is that easy.  Given that you have your interface already in the typelib format.  If you do not, we must examine Handwritten Proxies.

Handwritten Proxies

Before we start off a word of caution.  DON'T DO THIS.  It is really evil.  You will go out of sync with your proxy and core dump ever where it is used.  Make a xpidl, run it through the compiler, and create a typelib.  You will be happier, I will be happier, the universe will be happier.

If you are still reading I will assume that you love living on the cutting edge.  Well, okay here goes: Handwritten proxies are pretty trivial to create.  It is maintenance, which may get you into trouble.  For instance, if you change your interface around (of course you have not published it yet) and forget to change your handwritten proxy, you will blow up.  There is zero type checking when using handwritten proxies (as well as non-handwritten proxy, but you are less likely to go out of sync).

What you need to do to create a proxy?  One way to learn this is to look at an example-handwritten class.  We are going to look at the nsAppShellService.  You can take a look at the real header here:

http://lxr.mozilla.org/seamonkey/source/xpfe/appshell/public/nsIAppShellService.h

We will create a new class that implements this interface above and for each meathod class directly into nsProxyEvent.  Some macros are provided to help you out.
 
static NS_DEFINE_IID(kIAppShellServiceIID,   NS_IAPPSHELL_SERVICE_IID);
 

NS_IMPL_PROXY(nsAppShellService, nsIAppShellService);   // our macro
NS_IMPL_ISUPPORTS(nsAppShellService, kIAppShellServiceIID);
 

NS_IMETHODIMP nsAppShellService::Initialize(void) 
{
    return mProxyObject.Post( 3,            // call meathod number
                                                0,            // number of params
                                                 nsnull  );    //  parameters

}

The macro NS_IMPL_PROXY will setup the construct for in handwritten proxy object.  The constructor looks something like this:

nsAppShellService::nsAppShellService (PLEventQueue *destQueue, nsISupports *realObject)

Again, you have to know the event queue that this object will proxy to as well as the real object. If you would like the proxy to create the realObject for you, you can alternately use the following API:
 
nsProxyObject::nsProxyObject( PLEventQueue *destQueue, 
                              const nsCID &aClass, 
                              nsISupports *aDelegate, 
                              const nsIID &aIID);

Of course you may be wondering about the parameters fields of the Post() method.  This is where you have to do your own data marshalling of sorts.  Here is my example for CreateTopLevelWindow():
 
 
NS_IMETHODIMP nsAppShellProxy::CreateTopLevelWindow(nsIWebShellWindow * aParent,
                                                    nsIURL* aUrl, 
                                                    nsString& aControllerIID,
                                                    nsIWebShellWindow*& aResult, 
                                                    nsIStreamObserver* anObserver,
                                                    nsIXULWindowCallbacks *aCallbacks,
                                                    PRInt32 aInitialWidth,
                                                    PRInt32 aInitialHeight)
{

    nsXPTCVariant var[8];

    var[0].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[0].ptr     = aParent;

    var[1].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[1].ptr     = aUrl;

    var[2].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[2].ptr     = &aControllerIID;

    var[3].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[3].ptr     = &aResult;

    var[4].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[4].ptr     = anObserver;

    var[5].flags   = nsXPTCVariant::PTR_IS_DATA;
    var[5].ptr     = aCallbacks;

    var[6].flags   = 0;
    var[6].type    = nsXPTType::T_I32;
    var[6].val.i32 = aInitialWidth;

    var[7].flags   = 0;
    var[7].type    = nsXPTType::T_I32;
    var[7].val.i32 = aInitialHeight;

    nsresult rv = mProxyObject.Post( 6,            // call meathod number
                                     8,            // number of params
                                     var  );       //  parameters

    // store the out parameters

    aResult =  *((nsIWebShellWindow **)var[3].ptr);

    return rv;
}

Yuck is right.  You should have taken my advice above and generated yourself a typelib.  It really is not that bad.  You just have to stuff your variables into a nsXPTCVariant.   There is bug in the above example.  Can you point it out?  Well, the nsXPTCVariant is allocated on the stack. Even though Post() blocks, var is sometime stepped on.  My advice is to allocate this in the heap.
 
 
 
 
 
 



Copyright © 1998 The Mozilla Organization.