Creating a Python XPCOM component

  • Revision slug: Creating_a_Python_XPCOM_component
  • Revision title: Creating a Python XPCOM component
  • Revision id: 127028
  • Created:
  • Creator: Andreas Wuest
  • Is current revision? No
  • Comment The writing of this article does not meet MDC's quality standard

Revision Content

"Creating Applications with Mozilla" already provides a tutorial for making a simple javascript or C++ component (with the nsISimple interface), here is how to make the same component in Python.

(Note that some details may be missing)

Preparation

The PyXPCOM library is, normally, already provided with Mozilla. You do not want to download it from here - that is an old version, and won't work as well (I remember I had some problems with it, though I don't remember which ones). All you need to do is tell python where he can find the PyXPCOM library. This is probably best done by adding a "mozillalibs.pth" (any name goes) to your python site packages directory (mine is at "/usr/lib/python2.3/site-packages"), with only one line, the location of the packages you may want to import (the "/dist/bin/python" of your mozilla source).

Then you can do

import xpcom

in any Python module (mostly, you'll do it in your component).

You will also need your Mozilla to handle Python. This may require special compilation (at least, it did so for me, I recompiled the whole damn thing, it takes hours). Apparently it's possible to [the PyXPCOM stuff seperately], I haven't tried. I did this with Deer Park 1.5 Beta 2 (recommended for extension development), it should be possible with older versions, but details might vary (That shouldn't matter much, since I haven't recorded all the details here).

Anyway, to build Mozilla, you should get and compile the source, with the right options in the .mozconfig file (in the root of your mozilla source). I used the following:

. $topsrcdir/browser/config/mozconfig
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/fb-opt-static
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-static
ac_add_options --disable-shared
ac_add_options --disable-tests
ac_add_options --disable-short-wchar
ac_add_options --enable-extensions=default,python/xpcom

(The two last lines are the most important for us)

Then, launch the build !

Defining the interface

Make a file named "py_simple.idl", to define the interface:

#include "nsISupports.idl"
[scriptable, uuid(2b324e9d-a322-44a7-bd6e-0d8c83d94883)]
interface nsIPySimple : nsISupports
{
    attribute string yourName;
    void write( );
    void change(in string aValue);
};

(this is the same as the nsISimple interface used here ; theoretically we could use the same file, as we can have several components sharing an interface)

You should pay special attention to types here - Python and JavaScript are both loosely-typed, so it's fairly easy to expect to be able to pass anything from one to the other. I got bit by that one, sending a unicode string from javascript and expecting to receive a unicode string in Python, whereas only a "string" was defined in the interface file (use a wstring if you want unicode. See this discussion for a bit of info on unicode and javascript).

See here for info on describing interfaces, and on which types can be used.

Registring the interface

In the "components" directory, execute :

../xpidl -m typelib -w -v -I /usr/share/idl/mozilla/ nsIPySimple.idl

xpidl will then create nsIPySimple.xpt (which has to be in the right place, for example in the "components" directory).

Implementing the component

This is way simpler than with C++ ! PyXPCOM does a lot of the work for you.

Make a file named "py_simple.py" for the actual code (again in the 'components': directory)

from xpcom import components, verbose

class PySimple: #PythonTestComponent
    _com_interfaces_ = components.interfaces.nsIPySimple
    _reg_clsid_ = "{c456ceb5-f142-40a8-becc-764911bc8ca5}"
    _reg_contractid_ = "@mozilla.org/PySimple;1"
    def __init__(self):
        self.yourName = "a default name" # or mName ?

    def __del__(self):
        if verbose:
            print "PySimple: __del__ method called - object is destructing"

    def write(self):
        print self.yourName

    def change(self, newName):
        self.yourName = yourName

That's all! Then, you have to register your component; the procedure is the same as for any component, but this won't work if Python components weren't enabled.

To register the component, touch the .autoreg in the bin directory (it's a hidden file). This can also be done by deleting xpti.dat. Then, the next time Mozilla starts, it will rebuild the index of components, including any new one in the 'components' directory (Thus, it's better to start mozilla from the command line to see if new components register successfully).

Testing it

To see this work, you will have to start Firefox from the command line, since that'll be where the stuff will be printed out.

External links

Revision Source

<p>
</p><p>"<a class="external" href="http://books.mozdev.org/html">Creating Applications with Mozilla</a>" already provides  <a class="external" href="http://books.mozdev.org/html/mozilla-chp-8-sect-2.html">a tutorial </a> for making a simple javascript or C++ component (with the nsISimple interface), here is how to make the same component in Python.
</p><p>(Note that some details may be missing)
</p>
<h4 name="Preparation"> Preparation </h4>
<p>The PyXPCOM library is, normally, already provided with Mozilla. You do <b>not</b> want to download it <a class="external" href="http://aspn.activestate.com/ASPN/Downloads/Komodo/PyXPCOM/">from here</a>  - that is an old version, and won't work as well (I remember I had some problems with it, though I don't remember which ones). All you need to do is tell python where he can find the PyXPCOM library. This is probably best done by adding a "mozillalibs.pth" (any name goes) to your python site packages directory (mine is at "/usr/lib/python2.3/site-packages"), with only one line, the location of the packages you may want to import (the "/dist/bin/python" of your mozilla source).
</p><p>Then you can do
</p>
<pre class="eval">import xpcom
</pre>
<p>in any Python module (mostly, you'll do it in your component).
</p><p>You will also need your Mozilla to handle Python. This may require special compilation (at least, it did so for me, I recompiled the whole damn thing, it takes hours). Apparently it's possible to [<a class="external" href="http://kb.mozillazine.org/Standalone_PyXPCOM|build">the PyXPCOM stuff seperately</a>], I haven't tried. I did this with Deer Park 1.5 Beta 2 (recommended for extension development), it should be possible with older versions, but details might vary (That shouldn't matter much, since I haven't recorded all the details here).
</p><p>Anyway, to build Mozilla, you should <a href="en/Build"> get and compile the source</a>, with the right options in the .mozconfig file (in the root of your mozilla source). I used the following:
</p>
<pre class="eval">. $topsrcdir/browser/config/mozconfig
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/fb-opt-static
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-static
ac_add_options --disable-shared
ac_add_options --disable-tests
ac_add_options --disable-short-wchar
ac_add_options --enable-extensions=default,python/xpcom
</pre>
<p>(The two last lines are the most important for us)
</p><p>Then, launch the build !
</p>
<h4 name="Defining_the_interface"> Defining the interface </h4>
<p>Make a file named "py_simple.idl", to define the interface:
</p>
<pre>#include "nsISupports.idl"
[scriptable, uuid(2b324e9d-a322-44a7-bd6e-0d8c83d94883)]
interface nsIPySimple : nsISupports
{
    attribute string yourName;
    void write( );
    void change(in string aValue);
};
</pre>
<p>(this is the same as the nsISimple interface used <a class="external" href="http://books.mozdev.org/html/mozilla-chp-8-sect-2.html">here </a>; theoretically we could use the same file, as we can have several components sharing an interface)
</p><p>You should pay special attention to types here - Python and JavaScript are both loosely-typed, so it's fairly easy to expect to be able to pass anything from one to the other. I got bit by that one, sending a unicode string from javascript and expecting to receive a unicode string in Python, whereas only a "string" was defined in the interface file (use a wstring if you want unicode. See <a class="external" href="http://aspn.activestate.com/ASPN/Mail/Message/pyxpcom/2484414">this discussion</a> for a bit of info on unicode and javascript).
</p><p>See <a class="external" href="http://www.mozilla.org/scriptable/xpidl/idl-authors-guide/rules.html">here</a> for info on describing interfaces, and on which types can be used.
</p>
<h5 name="Registring_the_interface"> Registring the interface </h5>
<p>In the "components" directory, execute :
</p>
<pre>../xpidl -m typelib -w -v -I /usr/share/idl/mozilla/ nsIPySimple.idl
</pre>
<p>xpidl will then create nsIPySimple.xpt (which has to be in the right place, for example in the "components" directory).
</p>
<h4 name="Implementing_the_component"> Implementing the component </h4>
<p>This is <i>way</i> simpler than with C++ ! PyXPCOM does a lot of the work for you.
</p><p>Make a file named "py_simple.py" for the actual code (again in the 'components': directory)
</p>
<pre class="eval">from xpcom import components, verbose

class PySimple: #PythonTestComponent
    _com_interfaces_ = components.interfaces.nsIPySimple
    _reg_clsid_ = "{c456ceb5-f142-40a8-becc-764911bc8ca5}"
    _reg_contractid_ = "@mozilla.org/PySimple;1"
    def __init__(self):
        self.yourName = "a default name" # or mName ?

    def __del__(self):
        if verbose:
            print "PySimple: __del__ method called - object is destructing"

    def write(self):
        print self.yourName

    def change(self, newName):
        self.yourName = yourName
</pre>
<p>That's all! Then, you have to register your component; the procedure is the same as for any component, but this won't work if Python components weren't enabled.
</p><p>To register the component, touch the .autoreg in the bin directory (it's a hidden file). This can also be done by deleting xpti.dat. Then, the next time Mozilla starts, it will rebuild the index of components, including any new one in the 'components' directory (Thus, it's better to start mozilla from the command line to see if new components register successfully).
</p>
<h4 name="Testing_it"> Testing it </h4>
<p>To see this work, you will have to start Firefox from the command line, since that'll be where the stuff will be printed out.
</p>
<h3 name="External_links"> External links </h3>
<ul><li> <a class="external" href="http://books.mozdev.org/html/mozilla-chp-8-sect-2.html">Creating XPCOM components</a>, on which this short tutorial is based.
</li><li> A three-parts tutorial on <a class="external" href="http://www-128.ibm.com/developerworks">ibm developWorks</a>:
<ul><li> <a class="external" href="http://www-128.ibm.com/developerworks/webservices/library/co-pyxp1/">Getting to know PyXPCOM</a> - info on building PyXPCOM (and maybe Mozilla) to get it to work.
</li><li> <a class="external" href="http://www-128.ibm.com/developerworks/webservices/library/co-pyxp2.html">Getting started with PyXPCOM, part 2</a> - accessing xpcom from Python.
</li><li> <a class="external" href="http://www-128.ibm.com/developerworks/webservices/library/co-pyxp3/">Getting started with PyXPCOM, part 3</a> - Creating your own compomnents. The problem with this one is that the <a class="external" href="http://www-128.ibm.com/developerworks/webservices/library/co-pyxp3/listing2.html">sample code they give</a> is slightly broken (it ends with a "retu" which shouldn't be there. I haven't tried it, but it looks like nothing is missing, so getting rid of the "retu" should make it work fine.).
</li></ul>
</li></ul>
Revert to this revision