Difference between revisions of "Real World Mozilla First XPCOM Component"

From CDOT Wiki
Jump to: navigation, search
(Defining an Interface)
(nsISupports)
 
(68 intermediate revisions by 6 users not shown)
Line 1: Line 1:
 +
[[Real World Mozilla]] > [[Real World Mozilla Day 3]] > First XPCOM Component
 +
 
= Introduction =
 
= Introduction =
  
Line 11: Line 13:
 
* Reuse, as separate components become usable in different applications
 
* Reuse, as separate components become usable in different applications
  
This raises a question: what are 'XPCOM components' components of exactly?  The answer is Gecko, Mozilla's standards compliant, embeddable web browser and toolkit for creating web browsers and other applications.  XPCOM is the means of accessing Gecko library functionality and embedding or extending Gecko.
+
This raises a question: what are 'XPCOM components' components of exactly?  The answer is Gecko, Mozilla's standards compliant, embeddable web browser and toolkit for creating web browsers and other applications.  XPCOM is the means of accessing Gecko library functionality and embedding or extending Gecko
 +
 
 +
Much has been [http://developer.mozilla.org/en/docs/XPCOM written about XPCOM] elsewhere, and you are encouraged to read more on the theory behind this technology in conjunction with this walkthrough.
  
 
= Writing FirstXpcom =
 
= Writing FirstXpcom =
  
In this walkthrough we will be creating a simple binary XPCOM component using the build system, called FirstXpcom.  While this component won't do very much yet, it will provide a starting point for many other types of components you may wish to build down the road.
+
In this walkthrough we will be creating a simple binary XPCOM component using the build system, called '''FirstXpcom'''.  While this component won't do very much yet, it will provide a starting point for many other types of components you may wish to build down the road.
 +
 
 +
The functionality of this component will become part of a Gecko-enabled application (in this case, a Firefox binary extension).  However, it is important to remember that this is only one of many possible uses for it.
  
The code this component will become part of a Gecko-enabled application (in this case, a Firefox binary extension). However, it is important to remember that this is only one of many possible uses for it.
+
'''NOTE: the following assumes you have used an objdir. Replace all occurrences of $(objdirwith your objdir name.'''
  
 
= What is a Component? =
 
= What is a Component? =
Line 28: Line 34:
 
It should also be noted that components can implement multiple interfaces.  This is something we'll return to later when we discuss '''interface querying'''.
 
It should also be noted that components can implement multiple interfaces.  This is something we'll return to later when we discuss '''interface querying'''.
  
= Starting FirstXpcom =
+
= Writing FirstXpcom =
  
For this walkthrough we will use the Mozilla build system to create our component.  It is also possible to us the '''Gecko SDK''' ([http://developer.mozilla.org/en/docs/How_to_build_a_binary_XPCOM_component_using_Visual_Studio instructions are here]).  NOTE: this assumes that you have already done a successful objdir-Firefox build.
+
For this walkthrough we will use the Mozilla build system to create our component.  It is also possible to use the '''Gecko SDK''' ([http://developer.mozilla.org/en/docs/How_to_build_a_binary_XPCOM_component_using_Visual_Studio instructions are here]).  NOTE: this assumes that you have already done a successful objdir-Firefox build.
  
 
== Creating Directories ==
 
== Creating Directories ==
Line 47: Line 53:
 
== Defining an Interface ==
 
== Defining an Interface ==
  
Now we need to define the component's public interface.  To do this you must create an '''IDL file''' (see http://developer.mozilla.org/en/docs/XPIDL).  The IDL file defines the component's public interface in a language neutral way.  Mozilla uses the '''XPIDL''' format (Cross Platform Interface Definition Language) to define a component's public interfaces rather than doing it in C++ header files directly.  Using IDL, these interfaces can be defined in a language- and machine-independent way. IDLs make it possible to define interfaces which can then be processed by tools to autogenerate language-dependent interface specifications.  The IDL files are used to generate C++ header files.
+
Now we need to define the component's public interface.  To do this you must create an '''IDL file''' (see http://developer.mozilla.org/en/docs/XPIDL).  The IDL file defines the component's public interface in a language neutral way.  Mozilla uses the '''XPIDL''' format (Cross Platform Interface Definition Language) to define a component's public interface rather than doing it in C++ header files directly.  Using IDL, these interfaces can be defined in a language- and machine-independent way. IDLs make it possible to define interfaces which can then be processed by tools to autogenerate language-dependent interface specifications.  The IDL files are used to generate C++ header files.
  
Create an IDL file for the component in the public directory::
+
Create an IDL file for the component in the public directory:
  
 
  $ cd mozilla/extensions/firstxpcom/public
 
  $ cd mozilla/extensions/firstxpcom/public
 
  $ touch IFirstXpcom.idl
 
  $ touch IFirstXpcom.idl
  
Here are the contents of the IFirstXpcom.idl
+
Here are the contents of '''IFirstXpcom.idl'''
  
 
  #include "nsISupports.idl"
 
  #include "nsISupports.idl"
 
   
 
   
  [scriptable, uuid(...see below...)]
+
  [scriptable, uuid(''...see below...'')]
 
  interface IFirstXpcom : nsISupports
 
  interface IFirstXpcom : nsISupports
 
  {
 
  {
Line 68: Line 74:
 
What does this interface say?  To begin, notice the use of '''nsISupports'''.  This is the fundamental interface that all XPCOM components must implement.  What does it do?  How is it defined?
 
What does this interface say?  To begin, notice the use of '''nsISupports'''.  This is the fundamental interface that all XPCOM components must implement.  What does it do?  How is it defined?
  
nsISupports (defined [http://developer.mozilla.org/en/docs/nsISupports here]) is the base interface for all XPCOM components (i.e., it is possible to pass any XPCOM component around as an nsISupports object).  The methods in nsISupports define basic bookkeeping for an interface's lifetime (it also defines a way to check at runtime if a component implements a given interface, but more on that later).
+
=== nsISupports ===
  
Components need to keep track of how many clients hold references to them via an interface.  We call this reference counting on an interface, and it is used to determine when a component can be safely unloaded so that it doesn't leak (i.e., no one holds a reference any more, but the interface is still in memory).
+
[http://developer.mozilla.org/en/docs/nsISupports nsISupports] is the base interface for all XPCOM components (i.e., it is possible to pass any XPCOM component around as an nsISupports object).  The [http://developer.mozilla.org/en/docs/nsISupports methods] in nsISupports define basic bookkeeping for an interface's lifetime (they also define a way to check at runtime if a component implements a given interface, but more on that later).
 +
 
 +
Components need to keep track of how many clients hold references to them via an interface.  This is known as '''reference counting''' on an interface, and it is used to determine when a component can be safely unloaded so that it doesn't leak (i.e., no one holds a reference any more, but the interface is still in memory).
  
 
The members of nsISupports (i.e., QueryInterface, AddRef, and Release) provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used.
 
The members of nsISupports (i.e., QueryInterface, AddRef, and Release) provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used.
  
One point worth mentioning is that pointers in XPCOM are to interfaces.  Interface pointers are known to implement nsISupports, so you can access all of the object lifetime and discovery functionality described above.
+
One point worth mentioning is that '''pointers in XPCOM are to interfaces'''.  Interface pointers are known to implement nsISupports, so you can access all of the object lifetime and discovery functionality described above.
  
 
So the first line above says, "include the interface for nsISupports (defined in nsISupports.idl) because I'll need it", and the fourth line says, "I'm a new interface called IFirstXpcom, but I'm also nsISupports because I inherit from it."
 
So the first line above says, "include the interface for nsISupports (defined in nsISupports.idl) because I'll need it", and the fourth line says, "I'm a new interface called IFirstXpcom, but I'm also nsISupports because I inherit from it."
  
What does line three mean?  This says that our component is '''scriptable''', and can be used or implemented in scripting languages, JavaScript for example (see  http://developer.mozilla.org/en/docs/Interfaces:About_Scriptable_Interfaces).
+
=== UUID ===
 +
 
 +
[scriptable, uuid(...see below...)]
 +
 
 +
What does line 3 mean?  This says that our component is '''scriptable''', and can be used or implemented in scripting languages, JavaScript for example (see  http://developer.mozilla.org/en/docs/Interfaces:About_Scriptable_Interfaces).
 +
 
 +
Each interface needs to be uniquely identifiable, and Mozilla uses a 128-bit number called a '''UUID''' (Universally Unique Identifier) for this purpose.  You can generate one in a number of ways:
 +
 
 +
* at the command prompt using the command '''uuidgen''':
  
Each interface needs to be uniquely identifiable, and Mozilla uses a 128-bit number called a '''UUID''' (Universally Unique Identifier) for this purpose.  You can generate one using a number of methods:
+
$ uuidgen
 +
78af1749-014a-47aa-baec-2669670b7601
  
* in MSYS, use the command-line program '''uuidgen.exe'''
 
 
* in IRC ask firebot:
 
* in IRC ask firebot:
  
Line 91: Line 107:
 
  [scriptable, uuid(78af1749-014a-47aa-baec-2669670b7601)]
 
  [scriptable, uuid(78af1749-014a-47aa-baec-2669670b7601)]
  
Next comes the body of your interface.  Our interface defines one attribute and one method.  An attribute is a value you can '''Get''' and '''Set''' (NOTE: you can specify attributes that are Get only, that is read-only).  We have an attribute called '''name''' of type '''AString''' (a unicode, or two-byte string class.  For more details about strings in Mozilla, see http://developer.mozilla.org/en/docs/XPCOM_string_guide).
+
More information about generating UUIDs in different forms is available [http://developer.mozilla.org/En/Generating_GUIDs here].
  
Our interface also defines a single method called '''add''', which takes two long integers as input, adds them, and returns the result as a long integer.
+
=== Attributes and Methods ===
  
Because we are using the Mozilla build system to help us create our component, we can get it to translate our IDL into .h and .cpp stub files automatically.  But first we have to generate some makefiles.
+
attribute AString name;
 +
 +
long add(in long a, in long b);
  
== Makefile.in ==
+
Next comes the body of your interface. Our interface defines one '''attribute''' and one '''method'''.  An attribute is a value you can '''Get''' and '''Set''' (NOTE: you can specify attributes that are Get only, that is read-only).  We have an attribute called '''name''' of type '''AString''' (a unicode, or two-byte string class.  For more details about strings in Mozilla, see the [http://developer.mozilla.org/en/docs/XPCOM_string_guide XPCOM String Guide]).
  
The first step in making the build system aware of our component is to generate an input file for autoconf to use during the configure step, which will build our Makefile automatically.
+
Our interface also defines a single method called '''add''', which takes two long integers as input, adds them, and returns the result as a long integer; we'll write that code below.
  
  $ cd mozilla/extensions/firstxpcom
+
Because we are using the Mozilla build system to help us create our component, we can get it to translate our IDL into .h and .cpp stub files automatically. To do this we first have to generate some makefiles.
$ touch Makefile.in
 
  
The Makefile.in should contain the following (NOTE: you can read more about what these files actually mean [http://developer.mozilla.org/en/docs/How_Mozilla%27s_build_system_works here]):
+
== Build system changes ==
  
DEPTH = ../..
+
The first step in making the build system aware of our component is to generate an input file for autoconf to use during the configure step, which will build the necessary Makefile automatically.
topsrcdir = @top_srcdir@
 
srcdir = @srcdir@
 
VPATH = @srcdir@
 
  
include $(DEPTH)/config/autoconf.mk
+
=== mozilla/extensions/firstxpcom/Makefile.in ===
  
MODULE = firstxpcom
+
The Makefile.in should contain the following (NOTE: you can read more about what these files actually mean [http://developer.mozilla.org/en/docs/How_Mozilla%27s_build_system_works here]):
  
DIRS = public \
+
DEPTH = ../..
  src \
+
topsrcdir = @top_srcdir@
  $(NULL)
+
srcdir = @srcdir@
 +
VPATH = @srcdir@
 +
 +
include $(DEPTH)/config/autoconf.mk
 +
 +
MODULE = firstxpcom
 +
 +
DIRS = public \
 +
  src \
 +
  $(NULL)
 +
 +
XPI_NAME = firstxpcom
 +
 +
# A Unique ID for your extension
 +
INSTALL_EXTENSION_ID = firstxpcom@senecac.on.ca
 +
 +
# Will create a .xpi in /mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/
 +
XPI_PKGNAME = firstxpcom
 +
 +
# install.rdf will tell Firefox how to install our extension and
 +
# this says, "copy install.rdf into our extension dir and xpi"
 +
DIST_FILES = install.rdf
 +
 +
include $(topsrcdir)/config/rules.mk
  
XPI_NAME = firstxpcom
+
Note the '''DIRS''' variable.  It says that the two directories, public and src, will be entered during the build.  Because Mozilla's build system [http://developer.mozilla.org/en/docs/How_Mozilla%27s_build_system_works uses recursive make], we also need Makefile.in files in each of these.
  
# A Unique ID for your extension
+
=== mozilla/extensions/firstxpcom/public/Makefile.in ===
INSTALL_EXTENSION_ID = firstxpcom@senecac.on.ca
 
  
# Will create a .xpi in /mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/
+
Next we need a Makefile.in the '''public''' directory
XPI_PKGNAME = firstxpcom
 
  
# install.rdf will tell Firefox how to install our extension and  
+
DEPTH = ../../..
# this says, "copy install.rdf into our extension dir and xpi"
+
topsrcdir = @top_srcdir@
DIST_FILES = install.rdf
+
srcdir = @srcdir@
 +
VPATH = @srcdir@
 +
 +
include $(DEPTH)/config/autoconf.mk
 +
 +
MODULE = firstxpcom
 +
XPIDL_MODULE = firstxpcom
 +
 +
XPI_NAME = firstxpcom
 +
 +
# The files under EXPORTS are copied directly to
 +
# /mozilla/$(MOZ_OBJDIR)/dist/include/firstxpcom
 +
# and are thus accessible from other modules
 +
#EXPORTS      = \
 +
# myHeader.h \
 +
# $(NULL)
 +
 +
XPIDLSRCS = IFirstXpcom.idl
 +
 +
include $(topsrcdir)/config/rules.mk
  
include $(topsrcdir)/config/rules.mk
+
Here we tell the build system about our component's name and where its XPIDL file can be found.
  
 +
=== mozilla/extensions/firstxpcom/src/Makefile.in ===
  
Note the DIRS variable.  It says that the two directories, public and src, will be entered.  We also need Makefile.in files in each of those.
+
Now a Makefile.in in the '''src''' directory:
  
Next we need a Makefile.in public directory
+
DEPTH = ../../..
 
+
topsrcdir = @top_srcdir@
-----------start--------------
+
srcdir = @srcdir@
 
+
VPATH = @srcdir@
DEPTH = ../../..
+
topsrcdir = @top_srcdir@
+
include $(DEPTH)/config/autoconf.mk
srcdir = @srcdir@
+
VPATH = @srcdir@
+
IS_COMPONENT = 1
 +
MODULE = firstxpcom
 +
LIBRARY_NAME = firstxpcom
 +
 +
XPI_NAME = firstxpcom
 +
 +
# The REQUIRES section tells make which modules your
 +
# components uses. This causes the relevant subdirectories
 +
# of /mozilla/$(MOZ_OBJDIR)/dist/include/ to be added to the
 +
# C++ compiler's include path. If you're including Mozilla
 +
# headers and the compiler isn't finding them, it could well
 +
# mean that you haven't listed all of the necessary modules here.
 +
REQUIRES = xpcom \
 +
  string \
 +
  $(NULL)
 +
 +
# The .cpp source files to be compiled
 +
CPPSRCS = FirstXpcom.cpp \
 +
  $(NULL)
 +
include $(topsrcdir)/config/rules.mk
 +
 +
EXTRA_DSO_LDOPTS += \
 +
  $(XPCOM_GLUE_LDOPTS) \
 +
  $(NSPR_LIBS) \
 +
  $(NULL)
  
include $(DEPTH)/config/autoconf.mk
+
=== mozilla/extensions/firstxpcom/install.rdf ===
  
MODULE = firstxpcom
+
The last build-related file we need to write is a file telling Firefox's addon manager about our extension and how to install it--'''install.rdf''' (see http://developer.mozilla.org/en/docs/Install_Manifests for details):
XPIDL_MODULE = firstxpcom
 
  
XPI_NAME = firstxpcom
+
<pre>
 +
<?xml version="1.0" encoding="UTF-8"?>
 +
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 +
  xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 +
  <Description about="urn:mozilla:install-manifest">
 +
    <em:id>firstxpcom@senecac.on.ca</em:id>
 +
    <em:name>firstxpcom</em:name>
 +
    <em:version>0.1</em:version>
 +
    <em:creator>David Humphrey</em:creator>
 +
    <em:description>A Simple XPCOM Extension.</em:description>
 +
    <em:homepageURL>http://zenit.senecac.on.ca/wiki</em:homepageURL>
 +
    <em:targetApplication>
 +
      <Description>
 +
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->
 +
        <em:minVersion>2.0</em:minVersion>
 +
        <em:maxVersion>3.0b2pre</em:maxVersion> <!-- trunk build Nov 11, 2007 -->
 +
      </Description>
 +
    </em:targetApplication>
 +
  </Description>
 +
</RDF>
 +
</pre>
  
# The files under EXPORTS are copied directly to
+
== Building FirstXpcom ==
# /mozilla/$(MOZ_OBJDIR)/dist/include/myextension/
 
# and are thus accessible from other modules
 
#EXPORTS = \
 
# myHeader.h \
 
# $(NULL)
 
  
XPIDLSRCS = IFirstXpcom.idl
+
Now we're ready to build our component. Add the following line to your '''.mozconfig''' file in order to include firstxpcom during the build process:
  
include $(topsrcdir)/config/rules.mk
+
ac_add_options --enable-extensions=default,'''firstxpcom'''
  
----------end-------------------
+
Now you can (BUT DON'T, see below for quick way!!)re-build your tree (also known as "rebuilding world") by doing:
  
Here we tell the build system about our component's name and where it's XPIDL file can be found.
+
$ cd mozilla
 
+
$ make -f client.mk build
Now a Makefile.in for src
 
 
 
-----------start--------------
 
 
 
DEPTH = ../../..
 
topsrcdir = @top_srcdir@
 
srcdir = @srcdir@
 
VPATH = @srcdir@
 
 
 
include $(DEPTH)/config/autoconf.mk
 
 
 
IS_COMPONENT = 1
 
MODULE = firstxpcom
 
LIBRARY_NAME = firstxpcom
 
 
 
XPI_NAME = firstxpcom
 
 
 
# The REQUIRES section tells make which modules your
 
# components uses. This causes the relevant subdirectories
 
# of /mozilla/$(MOZ_OBJDIR)/dist/include/ to be added to the
 
# C++ compiler's include path. If you're including Mozilla
 
# headers and the compiler isn't finding them, it could well
 
# mean that you haven't listed all of the necessary modules here.
 
REQUIRES = xpcom \
 
  string \
 
  $(NULL)
 
 
 
# The .cpp source files to be compiled
 
CPPSRCS = FirstXpcom.cpp \
 
  $(NULL)
 
 
 
EXTRA_DSO_LDOPTS += \
 
  $(XPCOM_GLUE_LDOPTS) \
 
  $(NSPR_LIBS) \
 
  $(NULL)
 
 
 
include $(topsrcdir)/config/rules.mk
 
 
 
----------end-------------------
 
 
 
Last, we need to create a file telling Firefox how to install our extension, install.rdf (see http://developer.mozilla.org/en/docs/Install_Manifests for details).
 
 
 
-----------start--------------
 
 
 
<?xml version="1.0"?>
 
 
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 
    xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
  <Description about="urn:mozilla:install-manifest">
 
    <em:id>firstxpcom@senecac.on.ca</em:id>
 
    <em:version>0.1</em:version>
 
    <em:type>2</em:type>
 
 
 
 
    <em:targetApplication>
 
      <!-- Firefox -->
 
      <Description>
 
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
 
        <em:minVersion>2.0+</em:minVersion>
 
        <em:maxVersion>3.0a3</em:maxVersion>
 
      </Description>
 
    </em:targetApplication>
 
 
    <!-- front-end metadata -->
 
    <em:name>My First XPCOM Extension</em:name>
 
    <em:description>Just a simple XPCOM component.</em:description>
 
    <em:creator>David Humphrey</em:creator>
 
    <em:homepageURL>http://zenit.senecac.on.ca/wiki</em:homepageURL>
 
  </Description>
 
</RDF>
 
 
 
----------end-------------------
 
 
 
 
 
* Now let's build our component
 
 
 
Add the following line to your .mozconfig file in order to enable your extension in the build system:
 
 
 
ac_add_options --enable-extensions=default,firstxpcom
 
 
 
Now you can re-build your tree (also known as "rebuilding world") by doing:
 
 
 
$ cd mozilla
 
$ make -f client.mk build
 
  
 
BUT...this takes forever for it to actually reach your code, since it has to traverse the entire tree checking for changes.  So, you can bypass the rest of the tree and go directly to your component's build.
 
BUT...this takes forever for it to actually reach your code, since it has to traverse the entire tree checking for changes.  So, you can bypass the rest of the tree and go directly to your component's build.
  
$ cd $(objdir)
+
$ cd $(objdir)
$ ../build/autoconf/make-makefile extensions/firstxpcom
+
$ ../build/autoconf/make-makefile extensions/firstxpcom
  
 
Now call make in $(objdir)/extensions/firstxpcom
 
Now call make in $(objdir)/extensions/firstxpcom
  
$ cd $(objdir)/extensions/firstxpcom
+
$ cd $(objdir)/extensions/firstxpcom
$ make
+
$ make
  
This will create the public and src directories, as well as generate the Makefiles necessary in each.  Go into the public directory and call make again, in order to have your .h and .cpp stubs generated:
+
This will create the '''public''' and '''src''' directories, as well as generate the Makefiles necessary in each.  Go into the public directory and call make again, in order to have your .h and .cpp stubs generated (NOTE: this will cause errors, see below):
  
$ cd $(objdir)/extensions/firstxpcom/public
+
$ cd $(objdir)/extensions/firstxpcom/public
$ make
+
$ make
  
 
If all goes well, you'll see a hundred lines or so of output from make, ending in an error (NOTE: make will error out, since we have no source files yet).  We can safely ignore most of it, but a few lines are significant at this point:
 
If all goes well, you'll see a hundred lines or so of output from make, ending in an error (NOTE: make will error out, since we have no source files yet).  We can safely ignore most of it, but a few lines are significant at this point:
  
IFirstXpcom.idl
+
<code>
../../../dist/bin/xpidl.exe -m header -w -I../../../../extensions/firstxpcom/public -I../../../dist/idl -o _xpidlgen/IFirstXpcom /c/temp/proj/ff-trunk/mozilla/obj-fftrunk/extensions/firstxpcom/public/../../../../extensions/firstxpcom/public/IFirstXpcom.idl
+
IFirstXpcom.idl
 +
../../../dist/bin/xpidl.exe -m header -w -I../../../../extensions/firstxpcom/public -I../../../dist/idl -o _xpidlgen/IFirstXpcom  
 +
</code>
  
Here it is processing our IDL file.  As a result of this first partially successful run make, exported and generated header files (i.e.,from our IDL) will be placed in dist/include/firstxpcom.
+
Here we can see the build processing our IDL file.  As a result of this first (partially) successful run of make, exported and generated header files (i.e.,from our IDL) will be placed in '''$(objdir)/dist/include/firstxpcom'''.
  
Within $(obdir)/dist/include/firstxpcom/IFirstXpcom.h you'll see a block that begins:
+
Within '''$(objdir)/dist/include/firstxpcom/IFirstXpcom.h''' you'll see the following block of code:
  
#if 0
+
#if 0
/* Use the code below as a template for the implementation class for this interface. */
+
/* Use the code below as a template for the implementation class for this interface. */
...
+
...
/* End of implementation class template. */
+
/* End of implementation class template. */
#endif
+
#endif
  
The code in the middle of this block is what you want to start working with in order to implement your .cpp file (and .h if you choose to split it out, which we won't).  Copy this implementation stub into the following file:
+
The code in the middle of this block is what you want to use as the basis for implementing your .cpp file (and .h if you choose to split it out, which we won't).  Copy and paste this implementation stub into the following file: '''mozilla/extensions/firstxpcom/src/FirstXpcom.cpp'''
  
mozilla/extensions/firstxpcom/FirstXpcom.cpp
+
You'll need to do a search/replace on '''_MYCLASS_''' and change it to the name of your class, in our case, '''FirstXpcom'''.
 
 
You'll need to do a search/replace on _MYCLASS_ and change it to the name of your class.
 
 
 
_MYCLASS_ > FirstXpcom
 
  
 
Now you can try and re-run make for your extension:
 
Now you can try and re-run make for your extension:
  
$ cd $(objdir)/extensions/firstxpcom
+
$ cd $(objdir)/extensions/firstxpcom
$ make
+
$ make
  
This will produce a host of errors, mostly related to the fact that we don't have proper includes setup and it can't find declarations it needs.  You need to add an include for your interface's generated .h:
+
This will produce a host of errors, mostly related to the fact that we don't have proper includes set-up and it can't find declarations it needs.  You need to add an include for your interface's generated .h file:
  
#include "IFirstXpcom.h"
+
#include "IFirstXpcom.h"
  
Now re-run make.
+
Re-run make.
  
 +
''As an aside, and while you are thinking about Makefiles, you might take a moment to read [https://bugzilla.mozilla.org/show_bug.cgi?id=371201 bug 371201].  This is a bug that was identified while the author was trying to debug his Makefile.in files.  Ted Mielczarek (ted on IRC) was finally able to spot the problem--a trailing space on XPI_NAME.  I share this anecdote as a way to introduce https://bugzilla.mozilla.org, to emphasize the necessity of the community, and to show the kind of problems that one can have writing files for the build system.--UPDATE: this bug has now been fixed by Ted (March 26, 2007).''
  
Discuss https://bugzilla.mozilla.org/show_bug.cgi?id=371201 at this point, and problems I had making this work, how luser helped me, and in the end a bug was filed on the build system.
+
== Examining IFirstXpcom.h ==
  
Let's examine the code more closely and try to make sense of things ($(objdir)/dist/include/firstxpcom/IFirstXpcom.h):
+
Let's examine the code more closely and try to make sense of things.  Here is the code in '''$(objdir)/dist/include/firstxpcom/IFirstXpcom.h''':
  
-----------start--------------
+
/*
 
+
  * DO NOT EDIT.  THIS FILE IS GENERATED FROM c:/temp/proj/ff-trunk/mozilla/obj-fftrunk/extensions/firstxpcom/public/../../../../extensions/firstxpcom/public/IFirstXpcom.idl
/*
+
  */
* DO NOT EDIT.  THIS FILE IS GENERATED FROM c:/temp/proj/ff-trunk/mozilla/obj-fftrunk/extensions/firstxpcom/public/../../../../extensions/firstxpcom/public/IFirstXpcom.idl
+
*/
+
#ifndef __gen_IFirstXpcom_h__
 
+
#define __gen_IFirstXpcom_h__
#ifndef __gen_IFirstXpcom_h__
+
#define __gen_IFirstXpcom_h__
+
 
+
#ifndef __gen_nsISupports_h__
 
+
#include "nsISupports.h"
#ifndef __gen_nsISupports_h__
+
#endif
#include "nsISupports.h"
+
#endif
+
/* For IDL files that don't want to include root IDL files. */
 
+
#ifndef NS_NO_VTABLE
/* For IDL files that don't want to include root IDL files. */
+
#define NS_NO_VTABLE
#ifndef NS_NO_VTABLE
+
#endif
#define NS_NO_VTABLE
+
#endif
+
/* starting interface:    IFirstXpcom */
 
+
#define IFIRSTXPCOM_IID_STR "78af1749-014a-47aa-baec-2669670b7601"
/* starting interface:    IFirstXpcom */
+
#define IFIRSTXPCOM_IID_STR "78af1749-014a-47aa-baec-2669670b7601"
+
#define IFIRSTXPCOM_IID \
 
+
  {0x78af1749, 0x014a, 0x47aa, \
#define IFIRSTXPCOM_IID \
+
    { 0xba, 0xec, 0x26, 0x69, 0x67, 0x0b, 0x76, 0x01 }}
  {0x78af1749, 0x014a, 0x47aa, \
+
    { 0xba, 0xec, 0x26, 0x69, 0x67, 0x0b, 0x76, 0x01 }}
+
class NS_NO_VTABLE IFirstXpcom : public nsISupports {
 
+
  public:  
class NS_NO_VTABLE IFirstXpcom : public nsISupports {
+
public:  
+
  NS_DECLARE_STATIC_IID_ACCESSOR(IFIRSTXPCOM_IID)
 
+
  NS_DECLARE_STATIC_IID_ACCESSOR(IFIRSTXPCOM_IID)
+
  /* attribute AString name; */
 
+
  NS_IMETHOD GetName(nsAString & aName) = 0;
  /* attribute AString name; */
+
  NS_IMETHOD SetName(const nsAString & aName) = 0;
  NS_IMETHOD GetName(nsAString & aName) = 0;
+
  NS_IMETHOD SetName(const nsAString & aName) = 0;
+
  /* long add (in long a, in long b); */
 
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;
  /* long add (in long a, in long b); */
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;
+
};
 
+
};
+
  NS_DEFINE_STATIC_IID_ACCESSOR(IFirstXpcom, IFIRSTXPCOM_IID)
 
+
  NS_DEFINE_STATIC_IID_ACCESSOR(IFirstXpcom, IFIRSTXPCOM_IID)
+
/* Use this macro when declaring classes that implement this interface. */
 
+
#define NS_DECL_IFIRSTXPCOM \
/* Use this macro when declaring classes that implement this interface. */
+
  NS_IMETHOD GetName(nsAString & aName); \
#define NS_DECL_IFIRSTXPCOM \
+
  NS_IMETHOD SetName(const nsAString & aName); \
  NS_IMETHOD GetName(nsAString & aName); \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval);  
  NS_IMETHOD SetName(const nsAString & aName); \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval);  
+
/* Use this macro to declare functions that forward the behavior of this interface to another object. */
 
+
#define NS_FORWARD_IFIRSTXPCOM(_to) \
/* Use this macro to declare functions that forward the behavior of this interface to another object. */
+
  NS_IMETHOD GetName(nsAString & aName) { return _to GetName(aName); } \
#define NS_FORWARD_IFIRSTXPCOM(_to) \
+
  NS_IMETHOD SetName(const nsAString & aName) { return _to SetName(aName); } \
  NS_IMETHOD GetName(nsAString & aName) { return _to GetName(aName); } \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return _to Add(a, b, _retval); }  
  NS_IMETHOD SetName(const nsAString & aName) { return _to SetName(aName); } \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return _to Add(a, b, _retval); }  
+
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
 
+
#define NS_FORWARD_SAFE_IFIRSTXPCOM(_to) \
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
+
  NS_IMETHOD GetName(nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetName(aName); } \
#define NS_FORWARD_SAFE_IFIRSTXPCOM(_to) \
+
  NS_IMETHOD SetName(const nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->SetName(aName); } \
  NS_IMETHOD GetName(nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetName(aName); } \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return !_to ? NS_ERROR_NULL_POINTER : _to->Add(a, b, _retval); }  
  NS_IMETHOD SetName(const nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->SetName(aName); } \
+
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return !_to ? NS_ERROR_NULL_POINTER : _to->Add(a, b, _retval); }  
+
#if 0
 
+
/* Use the code below as a template for the implementation class for this interface. */
#if 0
+
/* Use the code below as a template for the implementation class for this interface. */
+
/* Header file */
 
+
class _MYCLASS_ : public IFirstXpcom
/* Header file */
+
{
class _MYCLASS_ : public IFirstXpcom
+
public:
{
+
  NS_DECL_ISUPPORTS
public:
+
  NS_DECL_IFIRSTXPCOM
  NS_DECL_ISUPPORTS
+
  NS_DECL_IFIRSTXPCOM
+
  _MYCLASS_();
 
+
  _MYCLASS_();
+
private:
 
+
  ~_MYCLASS_();
private:
+
  ~_MYCLASS_();
+
protected:
 
+
  /* additional members */
protected:
+
};
  /* additional members */
+
};
+
/* Implementation file */
 
+
NS_IMPL_ISUPPORTS1(_MYCLASS_, IFirstXpcom)
/* Implementation file */
+
NS_IMPL_ISUPPORTS1(_MYCLASS_, IFirstXpcom)
+
_MYCLASS_::_MYCLASS_()
 
+
{
_MYCLASS_::_MYCLASS_()
+
  /* member initializers and constructor code */
{
+
}
  /* member initializers and constructor code */
+
}
+
_MYCLASS_::~_MYCLASS_()
 
+
{
_MYCLASS_::~_MYCLASS_()
+
  /* destructor code */
{
+
}
  /* destructor code */
+
}
+
/* attribute AString name; */
 +
NS_IMETHODIMP _MYCLASS_::GetName(nsAString & aName)
 +
{
 +
    return NS_ERROR_NOT_IMPLEMENTED;
 +
}
 +
NS_IMETHODIMP _MYCLASS_::SetName(const nsAString & aName)
 +
{
 +
    return NS_ERROR_NOT_IMPLEMENTED;
 +
}
 +
 +
/* long add (in long a, in long b); */
 +
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
 +
{
 +
    return NS_ERROR_NOT_IMPLEMENTED;
 +
}
 +
 +
/* End of implementation class template. */
 +
#endif
 +
 +
 +
#endif /* __gen_IFirstXpcom_h__ */
 +
 +
What things do you notice?  What strikes you?
  
/* attribute AString name; */
+
=== Use of header guards ===
NS_IMETHODIMP _MYCLASS_::GetName(nsAString & aName)
 
{
 
    return NS_ERROR_NOT_IMPLEMENTED;
 
}
 
NS_IMETHODIMP _MYCLASS_::SetName(const nsAString & aName)
 
{
 
    return NS_ERROR_NOT_IMPLEMENTED;
 
}
 
  
/* long add (in long a, in long b); */
+
Header guards (see [http://en.wikipedia.org/wiki/Include_guard Include Guards]) are a portable technique for ensuring that includes or defines are only done once, for example, that nsISupports.h only be included once:
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
 
{
 
    return NS_ERROR_NOT_IMPLEMENTED;
 
}
 
  
/* End of implementation class template. */
+
#ifndef __gen_nsISupports_h__
#endif
+
#include "nsISupports.h"
 +
#endif
  
 +
Another way of doing this, especially with the Microsoft compiler, is to use
  
#endif /* __gen_IFirstXpcom_h__ */
+
  #pragma once
 
 
----------end-------------------
 
 
 
What things do you notice? What strikes you?
 
 
 
* Use of header guards
 
 
 
Header guards (or Include Guards, see http://en.wikipedia.org/wiki/Include_guard) are a portable technique for ensuring that includes or defines are only done once.  Another way of doing this, especially with the Microsoft compiler, is to use
 
 
 
#pragma once
 
  
 
But this is not portable, and therefore Mozilla doesn't use it.
 
But this is not portable, and therefore Mozilla doesn't use it.
  
 +
=== Use of macros ===
  
* Use of macros
+
Mozilla provides many convenience macros to help C++ developers do common tasks more efficiently.  Much of the code involved in making a module/component is generic, and has to be repeated every time.  You can spot macros in the code by the fact that they are all capitals, and often begin with '''NS_*'''.  Examples include:
  
Mozilla provides many convenience macros to help C++ developers do common tasks more efficiently.  Much of the code involved in making a module/component is generic, and has to be repeated ever time. You can spot macros in the code by the fact that they are all capitals, and often begin with NS_*. Examples include:
+
* NS_DECL_ISUPPORTS
 +
* NS_ENSURE_TRUE
 +
* NS_IMPL_NSGETMODULE
 +
* see http://developer.mozilla.org/en/docs/Category:XPCOM_Macros for more examples
  
NS_DECL_ISUPPORTS
+
NS_DECL_ appended with any interface name in all caps will declare all of the methods of that interface for you (nsIFoo --> NS_DECL_NSIFOO).  Looking at the code above shows you how this is possible.  For example, NS_DECL_NSIFOO will declare all of the methods of nsIFoo, provided that it exists and that nsIFoo.h was generated by the XPIDL compiler. Consider the following real class:
NS_ENSURE_TRUE
 
NS_IMPL_NSGETMODULE
 
(see http://developer.mozilla.org/en/docs/Category:XPCOM_Macros for more examples)
 
  
NS_DECL_ appended with any interface name in all caps will declare all of the methods of that interface for you.  Looking at the code above shows you how this is possible. For example, NS_DECL_NSIFOO will declare all of the methods of nsIFoo provided that it exists and that nsIFoo.h was generated by the XPIDL compiler. Consider the following real class:
+
  class myClass : public nsISomeClass
 
+
{
class myClass : public nsISomeClass
+
  public:
{
+
    NS_DECL_ISUPPORTS // declares AddRef, Release, and QueryInterface
  public:
+
    NS_DECL_NSISOMECLASS // declares all methods of nsISomeClass  
    NS_DECL_ISUPPORTS // declares AddRef, Release, and QueryInterface
+
    NS_DECL_NSISOMECLASS // declares all methods of nsISomeClass
+
    myClass();
 
+
    virtual ~myClass() {}
    myClass();
+
};
    virtual ~myClass() {}
 
};
 
  
The declaration of nsISomeClass doesn't include any methods other than the contructor and destructor. Instead, the class uses the NS_DECL_ macro
+
The declaration of nsISomeClass doesn't include any methods other than the constructor and destructor. Instead, the class uses the NS_DECL_ macro
  
Also note the use of NS_METHOD and NS_METHODIMP for return type signatures.  All XPCOM functions are required to return a result code (nsresult), which indicates whether or not the function worked (e.g., NS_OK).
+
Also note the use of NS_METHOD and NS_METHODIMP for return type signatures.  All XPCOM functions are required to return a result code ([http://bonsai.mozilla.org/cvsblame.cgi?file=/mozilla/xpcom/base/nscore.h&rev=1.103&raw=1&mark=324-327&#324 nsresult], a integer), which indicates whether or not the function worked (e.g., NS_OK).
  
 
Next there is NS_IMPL_ISUPPORTS1.  This macro implements the nsISupports interface for you, specifically the implementation of AddRef, Release, and QueryInterface for any object.
 
Next there is NS_IMPL_ISUPPORTS1.  This macro implements the nsISupports interface for you, specifically the implementation of AddRef, Release, and QueryInterface for any object.
  
NS_IMPL_ISUPPORTS1(classname, interface1)
+
NS_IMPL_ISUPPORTS1(classname, interface1)
  
Also, if your class implements more than one interface, you can simply change the number 1 in the macro to the number of interfaces you support and list the interfaces, separated by commas. For example:
+
If your class implements more than one interface, you can simply change the number 1 in the macro to the number of interfaces you support and list the interfaces, separated by commas. For example:
  
NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
+
NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
NS_IMPL_ISUPPORTSn(classname, interface1, ..., interfacen)
+
NS_IMPL_ISUPPORTSn(classname, interface1, ..., interfacen)
  
These macros automatically add the nsISupports entry for you, so you don't need to do something like this:
+
These macros automatically add the nsISupports entry for you, so you ''don't'' need to do the following:
  
NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)
+
NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)
  
As an example, consider seamonkey/xpcom/io/nsBinaryStream.cpp (http://lxr.mozilla.org/seamonkey/source/xpcom/io/nsBinaryStream.cpp#62):
+
As an example, consider [http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/xpcom/io/nsBinaryStream.cpp&rev=1.32&raw=1&mark=65&#65  mozilla/xpcom/io/nsBinaryStream.cpp]:
  
62 NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream)
+
NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream)
  
 +
=== Use of never before-seen types ===
  
* Use of never before-seen types
+
A quick scan of the code also reveals types that will be unfamiliar, including: nsAString, nsresult, and PRInt32.  What are these?
  
nsAString, nsresult, PRInt32 -- what are these new types?
+
Because Mozilla is cross-platform almost all of the standard types you are used to have Mozilla-specific versions.  For example, PRInt32, which is defined as part of the Netscape Portable Runtime (hence the ''PR'' prefix), is a signed 32-bit integer, no matter the OS you are using (see http://developer.mozilla.org/en/docs/PRInt32).  Depending on the platform you use this could mean a regular int or a long.  The same is true of strings (Mozilla has it's own string classes -- see http://developer.mozilla.org/en/docs/XPCOM_string_guide) because of the need for multi-language support and other things necessary to make Mozilla's products work around the world. 
  
Because Mozilla is cross-platform almost all of the standard types you are used to have Mozilla-specific versions.  For example, PRInt32, which is defined as part of the Netscape Portable Runtime (hence the PR prefix), is a signed 32-bit integer on all platforms (see http://developer.mozilla.org/en/docs/PRInt32).  Depending on the platform you use this could mean a regular int or a long.  The same is true of strings (Mozilla has it's own string classes -- see http://developer.mozilla.org/en/docs/XPCOM_string_guide) because of the need for multi-language support and other things necessary to make Mozilla's products work.  At first there are so many of these to learn.  But you quickly get accustomed to them, and looking at how other people code (via lxr) can help you in this process.
+
At first there are so many of these to learn.  But you quickly get accustomed to them, and looking at how other people code (via [http://lxr.mozilla.org lxr]) can help you in this process.
  
 +
=== Strange return types ===
  
* Strange return types - differences between original IDL and the C++ signatures
+
You'll also notice differences between the original IDL and the autogenerated C++ signaturesIn our IDL file, the IFirstXpcom::Add method took two longs and returned a long. However in the C++ code stub it says something different:
 
 
In our IDL file, the IFirstXpcom::Add method took two longs and returned a long. However in the C++ code stub it says something different:
 
  
 
   /* XPIDL -- long add (in long a, in long b); */
 
   /* XPIDL -- long add (in long a, in long b); */
 
   NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;
 
   NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;
  
The return value of XPCOM methods generated from XPIDL is always of the type nsresult, and the small macro used in these expansions, NS_IMETHOD, actually represents nsresult. nsresult is returned even when in XPIDL you specify that the method return a void. If your IDL requires a return type, as ours does, that value will be added as a final parameter to the call list--in this case PRInt32 *_retval.
+
The return value of XPCOM methods generated from XPIDL is always of the type '''nsresult''', and the small macro used in these expansions, '''NS_IMETHOD''', actually represents nsresult. nsresult is returned even when in XPIDL you specify that the method return a void. If your IDL requires a return type, as ours does, that value will be added as a final parameter to the call list--in this case '''PRInt32 *_retval'''.
  
There are other things you should know about making your code compatible on all of the platforms Mozilla supports.  You can read about them here: http://www.mozilla.org/hacking/portable-cpp.html.
+
There are other things you should know about making your code compatible on all of the supported Mozilla platforms.  You can read about them here: http://www.mozilla.org/hacking/portable-cpp.html.
  
 +
== Module Code ==
  
Continuing on, we now have to write a bit of code to get our component registered.  Components reside in modules, and those modules are defined in shared library files (i.e., DLLs or DSOs) that typically sit in the components directory of an XPCOM application.
+
We now need to write a bit of code to get our component registered.  Components reside in modules, and those modules are defined in shared library files (i.e., DLLs or DSOs) that typically sit in the components directory of an XPCOM application.
  
A set of default libraries stored in this components directory makes up a typical Gecko installation, providing functionality that consists of networking, layout, composition, a cross-platform user interface, and others.  
+
When you build a component or module and compile it into a library, it must export a single method named '''NSGetModule'''. This NSGetModule function is the entry point for accessing the library. It gets called during registration and unregistration of the component, and when XPCOM wants to discover what interfaces or classes the module/library implements.
  
When you build a component or module and compile it into a library, it must export a single method named NSGetModule. This NSGetModule function is the entry point for accessing the library. It gets called during registration and unregistration of the component, and when XPCOM wants to discover what interfaces or classes the module/library implements.
+
In addition to implementing the module code (i.e., [http://developer.mozilla.org/en/docs/nsIModule nsIModule]), we also have to write code to allow our component to be created at runtime based on an interface rather than concrete types--essentially, abstracting the process of creation so that clients don't have to know about real classes underneath the interfaces.  This means implementing the [http://developer.mozilla.org/en/docs/nsIFactory nsIFactory] interface.
  
In addition to implementing the module code (i.e., nsIModule), we also have to write code to allow our component to be created at runtime based on interface rather than concrete types--esentially, abstracting the process of creation so that clients don't have to know about real classes underneath the interfacesThis means implementing the nsIFactory interface.
+
Together, these two interfaces will require us to write [http://developer.mozilla.org/en/docs/Creating_XPCOM_Components:Creating_the_Component_Code#webLock1.cpp hundreds lines of code], the majority of which is generic boilerplate codeIn order to simplify the work component developers must do, a number of macros help us with this task:
  
Together, these two interfaces will require us to write hundreds lines of code (see http://developer.mozilla.org/en/docs/Creating_XPCOM_Components:Creating_the_Component_Code#webLock1.cpp as an example), the majority of which is generic boilerplate code.  In order to simplify the work component developers must do, a number of macros help us with this task:
+
* NS_GENERIC_FACTORY_CONSTRUCTOR
 +
* NS_IMPL_NSGETMODULE
  
NS_GENERIC_FACTORY_CONSTRUCTOR
+
#include "nsIGenericFactory.h"
NS_IMPL_NSGETMODULE
+
...
 +
// This will result in a function named FirstXpcomConstructor.
 +
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)
 +
 +
// 19f3ef5e-759f-49a4-88e3-ed27f9c83011
 +
#define FIRSTXPCOM_CID \
 +
  {0x19f3ef5e, 0x759f, 0x49a4, \
 +
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }
 +
 +
static const nsModuleComponentInfo components[] =
 +
{
 +
  { "FirstXpcom",
 +
    FIRSTXPCOM_CID,
 +
    "@senecac.on.ca/firstxpcom;1",
 +
    FirstXpcomConstructor
 +
  }
 +
};
 +
 +
NS_IMPL_NSGETMODULE(FirstXpcomModule, components)
  
 +
First, we have to include '''nsIGenericFactory.h''' in order to get '''NS_GENERIC_FACTORY_CONSTRUCTOR'''.  Now we can add the following line, which will generate a function called '''FirstXpcomConstructor''':
  
-----------start--------------
+
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)
  
#include "nsIGenericFactory.h"
+
Note: we also could have provided an initialization function to be called after our object gets allocated (i.e., FirstXpcom->Init()):
...
 
  
// This will result in a function named FirstXpcomConstructor.
+
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)
 
  
// 19f3ef5e-759f-49a4-88e3-ed27f9c83011
+
Next, we need to create proper identification for our component's module so that it can be passed to the module implementation macro, '''NS_IMPL_NSGETMODULE'''.  This macro takes an array of '''nsModuleComponentInfo''' so that you can define more than one component per module (remember that a module is a collection of components, and every component belongs to a module so it can get loaded by the system).
#define FIRSTXPCOM_CID \
 
  {0x19f3ef5e, 0x759f, 0x49a4, \
 
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }
 
  
static const nsModuleComponentInfo components[] =
+
Start by generating another '''uuid''', which will be used for identifying our component/class (i.e., we can't re-use our interface's uuid), for example:
{
 
  { "FirstXpcom",
 
    FIRSTXPCOM_CID,
 
    "@senecac.on.ca/firstxpcom;1",
 
    FirstXpcomConstructor
 
  }
 
};
 
 
NS_IMPL_NSGETMODULE(FirstXpcomModule, components)
 
  
----------end-------------------
+
19f3ef5e-759f-49a4-88e3-ed27f9c83011
  
First, we have to include nsIGenericFactory.h in order to get NS_GENERIC_FACTORY_CONSTRUCTOR. Now we can add the following line, which will generate a function called FirstXpcomConstructor:
+
Now [http://developer.mozilla.org/En/Generating_GUIDs write a define] to make it easier to pass this Class ID around in C++:
  
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)
+
#define FIRSTXPCOM_CID \
 +
  {0x19f3ef5e, 0x759f, 0x49a4, \
 +
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }
  
Note: we also could have provided an initialization function to be called after our object gets allocated (i.e., FirstXpcom->Init()):
+
Then we can populate our array with a single entry for the FirstXpcom component:
  
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)
+
static const nsModuleComponentInfo components[] =
 +
{
 +
  { "FirstXpcom", // descriptive name
 +
    FIRSTXPCOM_CID, // CID from above
 +
    "@senecac.on.ca/firstxpcom;1", // Contract ID
 +
    FirstXpcomConstructor // Factory Constructor
 +
  }
 +
};
  
Next, we need to create proper identification for our component's module so that it can be passed to the module implementation macro NS_IMPL_NSGETMODULEThis macro takes an array of nsModuleComponentInfo so that you can define more than one component per module (remember that a module is a collection of components, and every component belongs to a module so it can get loaded by the system).
+
The last two entries need some explanation.  The '''Component ID''' is a human-readable string that clients can use to get/create an instance of your class.  We'll see how to do this later onHere is an example Component ID and what it means:
  
Start by generating another uuid that will be used for identifying our component/class, for example:
+
"@mozilla.org/network/ldap-operation;1"
  
19f3ef5e-759f-49a4-88e3-ed27f9c83011
+
* domain = @mozilla.org
 +
* module = network
 +
* component = ldap-operation
 +
* version = 1
  
Now write a define to make it easier to pass this Class ID around:
+
The final line, the constructor, is the name of the constructor automatically generated by  '''NS_GENERIC_FACTORY_CONSTRUCTOR'''.  It will be the name of your concrete class followed by "Constructor," in our case '''FirstXpcomConstructor'''.
  
#define FIRSTXPCOM_CID \
+
And that's it for the module/factory code.
  {0x19f3ef5e, 0x759f, 0x49a4, \
 
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }
 
  
Then we can populate our componets array with a single entry for the FirstXpcom component:
+
== FirstXpcom.cpp ==
  
static const nsModuleComponentInfo components[] =
+
All that remains is our implementation.  Here is the final version of '''FirstXpcom.cpp''' (emphasis added to highlight changes):
{
 
  { "FirstXpcom", // descriptive name
 
    FIRSTXPCOM_CID, // CID from above
 
    "@senecac.on.ca/firstxpcom;1", // Contract ID
 
    FirstXpcomConstructor // Factory Constructor
 
  }
 
};
 
  
The last two need some explanationThe Component ID is a human-readable string that clients can use to get/create an instance of your class.  We'll see how to do this later onHere is an example Component ID and what it means:
+
'''#include <stdio.h> // for printf()'''
 
+
  #include "IFirstXpcom.h" // the CPP .h generated from our .idl
"@mozilla.org/network/ldap-operation;1"
+
#include "nsIGenericFactory.h" // for NS_GENERIC_FACTORY_CONSTRUCTOR()
 
+
#include "nsStringAPI.h" // for nsString
domain = @mozilla.org
+
module = network
+
class FirstXpcom : public IFirstXpcom
component = ldap-operation
+
{
version = 1
+
public:
 
+
  NS_DECL_ISUPPORTS
The final line, the constructor, is the name of the constructor automatically generated by the NS_GENERIC_FACTORY_CONSTRUCTOR. It will be the name of your concrete class followed by "Constructor," in our case FirstXpcomConstructor.
+
  NS_DECL_IFIRSTXPCOM
 +
 +
  FirstXpcom();
 +
 +
private:
 +
  ~FirstXpcom();
 +
 +
protected:
 +
  '''nsString mName;'''
 +
};
 +
 +
NS_IMPL_ISUPPORTS1(FirstXpcom, IFirstXpcom)
 +
 +
FirstXpcom::FirstXpcom()
 +
{
 +
  /* member initializers and constructor code */
 +
  '''mName.Assign(NS_LITERAL_STRING("FirstXpcom Component"));'''
 +
  }
 +
 +
FirstXpcom::~FirstXpcom()
 +
{
 +
  /* destructor code */
 +
}
 +
 +
/* attribute AString name; */
 +
NS_IMETHODIMP FirstXpcom::GetName(nsAString & aName)
 +
{
 +
  '''aName.Assign(mName);'''
 +
  '''printf("FirstXpcom::GetName\n");'''
 +
  '''return NS_OK;'''
 +
  }
 +
 +
NS_IMETHODIMP FirstXpcom::SetName(const nsAString & aName)
 +
{
 +
  '''mName.Assign(aName);'''
 +
  '''printf("FirstXpcom::SetName\n");'''
 +
  '''return NS_OK;'''
 +
}
 +
 +
/* long add (in long a, in long b); */
 +
NS_IMETHODIMP FirstXpcom::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
 +
{
 +
  '''printf("FirstXpcom::Add(%d, %d)", a, b);'''
 +
  '''*_retval = a + b;'''
 +
  '''return NS_OK;'''
 +
}
 +
 +
// This will result in a function named FirstXpcomConstructor.
 +
'''NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)'''
 +
 +
'''// 19f3ef5e-759f-49a4-88e3-ed27f9c83011 '''
 +
'''#define FIRSTXPCOM_CID \'''
 +
  '''{0x19f3ef5e, 0x759f, 0x49a4, \'''
 +
      '''{ 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }'''
 +
 +
'''static const nsModuleComponentInfo components[] ='''
 +
'''{'''
 +
  '''{ "FirstXpcom",'''
 +
    '''FIRSTXPCOM_CID,'''
 +
    '''"@senecac.on.ca/firstxpcom;1",'''
 +
    '''FirstXpcomConstructor'''
 +
  '''}'''
 +
'''};'''
 +
 +
'''NS_IMPL_NSGETMODULE(FirstXpcomModule, components)'''
  
And that's it!  We've done it.  Time to call make again:
+
Time to call make again:
  
$ cd $(objdir)/extensions/firstxpcom
+
$ cd $(objdir)/extensions/firstxpcom
$ make
+
$ make
  
 
Assuming this works without errors, here's what has happened:
 
Assuming this works without errors, here's what has happened:
  
* Generated makefiles for your projects in extensions/firstxpcom/ (remember, we’re under /mozilla/$(MOZ_OBJDIR)/.
+
* Generated makefiles for your project were created in extensions/firstxpcom/ (remember, we're under /mozilla/$(MOZ_OBJDIR)/.
 
*  Exported header files and generated header files (from IDL) in dist/include/firstxpcom/
 
*  Exported header files and generated header files (from IDL) in dist/include/firstxpcom/
* Static libraries for your modules in dist/lib/ (in case other modules want to link statically to your stuff instead of using XPCOM).
+
* Static libraries for your modules in dist/lib/ (in case other modules want to link statically instead of using XPCOM).
 
* XPI file in dist/xpi-stage/firstxpcom.xpi.
 
* XPI file in dist/xpi-stage/firstxpcom.xpi.
* Everything else in dist/bin/extensions/firstxpcom@senecac.on.ca/.
+
* Everything else in dist/bin/extensions/firstxpcom@senecac.on.ca
  
Run Firefox and make sure you can see your extension in the addon manager:
+
= Testing FirstXpcom =
  
$ cd $(objdir)/dist/bin
+
== Checking the Add-on Manager ==
$ export MOZ_NO_REMOTE=1
 
$ export MOZ_DEBUG_BREAK=warn
 
$ firefox.exe -Profilemanager
 
  
Now let's try and access this from JS in the browserIf you haven't done so already, download the Extension Developer's Extension:
+
We'll write formal tests and code to use our component later.  For now, make sure it gets loaded into Firefox and is visible in the Addon ManagerRun Firefox and make sure you can see your extension in the addon manager:
  
http://ted.mielczarek.org/code/mozilla/extensiondev/index.html
+
$ cd $(objdir)/dist/bin
 +
$ export MOZ_DEBUG_BREAK=warn
 +
$ firefox.exe -Profilemanager -no-remote
  
This will allow you to use the JavaScript Shell inside the browser, making it easy to try out the firstxpcom component (see http://developer.mozilla.org/en/docs/Introduction_to_the_JavaScript_shell).
+
== Accessing FirstXpcom from the JavaScript Shell ==
  
Lauch the JSShell (Tools > Extension Developer > Javascript Shell) and write some code to access your XPCOM component:
+
Now let's try and access this from JavaScript in the browser.  If you haven't done so already, download and install the [http://ted.mielczarek.org/code/mozilla/extensiondev/index.html Extension Developer's extension].  This will allow you to use the [http://developer.mozilla.org/en/docs/Introduction_to_the_JavaScript_shell JavaScript Shell] inside the browser, making it easy to try out the firstxpcom component.
  
// Define our component's ID so we can create an instance of it below.
+
Launch the JS Shell ('''Tools > Extension Developer > Javascript Shell''') and write some code to access your XPCOM component.  You can work interactively without having to define functions, write a complete extension, etc.:
const cid = "@senecac.on.ca/firstxpcom;1"
 
print(cid)
 
  
// This will create an instance of our firstxpcom class and return it as nsISupports
+
* Define our component's ID so we can create an instance of it below.
var obj = Components.classes[cid].createInstance()
 
  
// This will take the nsISupports object returned above and QI it to IFirstXpcom
+
const cid = "@senecac.on.ca/firstxpcom;1"
obj = obj.QueryInterface(Components.interfaces.IFirstXpcom)
+
print(cid)
  
 +
* Now create an instance of our component.  The value of obj will be nsISupports at this point (i.e., we can't call IFirstXpcom's methods yet).
  
// Now we can use the IFirstXpcom methods and attributes
+
var obj = Components.classes[cid].createInstance()
var sum
 
sum = obj.add(4,5)
 
  
var name
+
* Next, take the the nsISupports object returned above and query it (i.e., see if it supports your interface type and if so, change to that interface) to IFirstXpcom, often referred to as QI (e.g., ''"...you need to QI it to IFirstXpcom..."'').
name = "Dave Humphrey"
 
  
obj.name = name
+
obj = obj.QueryInterface(Components.interfaces.IFirstXpcom)
print(obj.name)
+
 
alert(obj.name)
+
* At this point we have the ability to use the IFirstXpcom methods and attributes:
 +
 
 +
var sum
 +
sum = obj.add(4,5)
 +
 +
var name
 +
name = "FirstXpcom!"
 +
 +
obj.name = name
 +
print(obj.name)
 +
alert(obj.name)
  
When you run this code, also notice how your C++ printf statements are sending messages to stdout in your shell window (you may need to scrollback through all the other messages to find them).
+
When you run this code, also notice how your C++ '''printf''' statements are causing messages to stdout in your console window (you may need to scrollback through all the other messages to find them).
  
Let's try debugging FirstXpcom in C++ with Visual Studio.NET
+
= Resources =
  
* Close minefield
+
* [[Makefile.in Template for In-Tree Extensions]]
* Open VS.NET and choose File > Open Project/Solution...
 
* Open $(objdir)/dist/bin/firefox.exe
 
* Right-click the firefox.exe item in the Solution Explorer and choose Properties
 
* Add the following to "Command Arguments": -p <development_profile_name> or -Profilemanager if you don't have one yet.
 
* Add the following to "Environment": XPCOM_DEBUG_BREAK=warn.  Click OK
 
* File > Open... mozilla/extensions/firstxpcom/src/FirstXpcom.cpp
 
* Set a breakpoint on ::Add -->  *_retval = a + b;
 
* Click "Start Debugging" (the "play" button)
 
* Follow the same steps above in the Javascript Shell in order to create and call add() on your component.
 
* Use F11 to move line by line when you get dropped into the debugger, and F5 to continue.
 

Latest revision as of 13:25, 18 November 2008

Real World Mozilla > Real World Mozilla Day 3 > First XPCOM Component

Introduction

Mozilla is a huge platform. In order to work in such a large system and have hundreds or thousands of developers working on the same code, it is necessary to break things into smaller pieces. In Mozilla these pieces are called components, and are shipped as .DLL files (on win32). When one of these .DLL files contains two or more components, it is called a module.

Mozilla developer specialize in one or more components/modules (modules are made up of components). Here is a list of the various modules and their owners and peers: http://www.mozilla.org/owners.html. Module owners are responsible for a module's code, and have final say about how changes are made to it. The peers are people designated by the module owner to help them maintain the code. Owners and peers do code reviews of patches from the community.

By writing code in components/modules instead of one giant application, there are a number of benefits:

  • Modularization of code, so pieces of a system can be developed, built, replaced independently
  • Increased performance, because you only need to load the components/modules you are using instead of the whole thing
  • Reuse, as separate components become usable in different applications

This raises a question: what are 'XPCOM components' components of exactly? The answer is Gecko, Mozilla's standards compliant, embeddable web browser and toolkit for creating web browsers and other applications. XPCOM is the means of accessing Gecko library functionality and embedding or extending Gecko.

Much has been written about XPCOM elsewhere, and you are encouraged to read more on the theory behind this technology in conjunction with this walkthrough.

Writing FirstXpcom

In this walkthrough we will be creating a simple binary XPCOM component using the build system, called FirstXpcom. While this component won't do very much yet, it will provide a starting point for many other types of components you may wish to build down the road.

The functionality of this component will become part of a Gecko-enabled application (in this case, a Firefox binary extension). However, it is important to remember that this is only one of many possible uses for it.

NOTE: the following assumes you have used an objdir. Replace all occurrences of $(objdir) with your objdir name.

What is a Component?

Components define certain publicly available functionality. The public portion of a component is defined in an Interface. Interfaces are a kind of contract between implementors (those writing code to implement the interface) and clients (those writing code to use an interface's implementation). By defining an interface for a component, we advertise to the world what we are willing and able to do. There are other advantages as well:

  • implementors can change their implementation without affecting clients
  • clients can choose between multiple implementations

It should also be noted that components can implement multiple interfaces. This is something we'll return to later when we discuss interface querying.

Writing FirstXpcom

For this walkthrough we will use the Mozilla build system to create our component. It is also possible to use the Gecko SDK (instructions are here). NOTE: this assumes that you have already done a successful objdir-Firefox build.

Creating Directories

Start by creating a directory for your component, using all lowercase letters:

$ cd mozilla/extensions
$ mkdir firstxpcom

Next, create 2 more directories to hold your component's public interface (i.e., public) and source code (i.e., src):

$ cd mozilla/extensions/firstxpcom
$ mkdir public
$ mkdir src

Defining an Interface

Now we need to define the component's public interface. To do this you must create an IDL file (see http://developer.mozilla.org/en/docs/XPIDL). The IDL file defines the component's public interface in a language neutral way. Mozilla uses the XPIDL format (Cross Platform Interface Definition Language) to define a component's public interface rather than doing it in C++ header files directly. Using IDL, these interfaces can be defined in a language- and machine-independent way. IDLs make it possible to define interfaces which can then be processed by tools to autogenerate language-dependent interface specifications. The IDL files are used to generate C++ header files.

Create an IDL file for the component in the public directory:

$ cd mozilla/extensions/firstxpcom/public
$ touch IFirstXpcom.idl

Here are the contents of IFirstXpcom.idl

#include "nsISupports.idl"

[scriptable, uuid(...see below...)]
interface IFirstXpcom : nsISupports
{
	attribute AString name;

	long add(in long a, in long b);
};

What does this interface say? To begin, notice the use of nsISupports. This is the fundamental interface that all XPCOM components must implement. What does it do? How is it defined?

nsISupports

nsISupports is the base interface for all XPCOM components (i.e., it is possible to pass any XPCOM component around as an nsISupports object). The methods in nsISupports define basic bookkeeping for an interface's lifetime (they also define a way to check at runtime if a component implements a given interface, but more on that later).

Components need to keep track of how many clients hold references to them via an interface. This is known as reference counting on an interface, and it is used to determine when a component can be safely unloaded so that it doesn't leak (i.e., no one holds a reference any more, but the interface is still in memory).

The members of nsISupports (i.e., QueryInterface, AddRef, and Release) provide the basic means for getting the right interface from an object, incrementing the reference count, and releasing objects once they are not being used.

One point worth mentioning is that pointers in XPCOM are to interfaces. Interface pointers are known to implement nsISupports, so you can access all of the object lifetime and discovery functionality described above.

So the first line above says, "include the interface for nsISupports (defined in nsISupports.idl) because I'll need it", and the fourth line says, "I'm a new interface called IFirstXpcom, but I'm also nsISupports because I inherit from it."

UUID

[scriptable, uuid(...see below...)]

What does line 3 mean? This says that our component is scriptable, and can be used or implemented in scripting languages, JavaScript for example (see http://developer.mozilla.org/en/docs/Interfaces:About_Scriptable_Interfaces).

Each interface needs to be uniquely identifiable, and Mozilla uses a 128-bit number called a UUID (Universally Unique Identifier) for this purpose. You can generate one in a number of ways:

  • at the command prompt using the command uuidgen:
$ uuidgen
78af1749-014a-47aa-baec-2669670b7601
  • in IRC ask firebot:
/msg firebot uuid 

You need to get a UUID and put it in the brackets, for example:

[scriptable, uuid(78af1749-014a-47aa-baec-2669670b7601)]

More information about generating UUIDs in different forms is available here.

Attributes and Methods

attribute AString name;

long add(in long a, in long b);

Next comes the body of your interface. Our interface defines one attribute and one method. An attribute is a value you can Get and Set (NOTE: you can specify attributes that are Get only, that is read-only). We have an attribute called name of type AString (a unicode, or two-byte string class. For more details about strings in Mozilla, see the XPCOM String Guide).

Our interface also defines a single method called add, which takes two long integers as input, adds them, and returns the result as a long integer; we'll write that code below.

Because we are using the Mozilla build system to help us create our component, we can get it to translate our IDL into .h and .cpp stub files automatically. To do this we first have to generate some makefiles.

Build system changes

The first step in making the build system aware of our component is to generate an input file for autoconf to use during the configure step, which will build the necessary Makefile automatically.

mozilla/extensions/firstxpcom/Makefile.in

The Makefile.in should contain the following (NOTE: you can read more about what these files actually mean here):

DEPTH		= ../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE 	= firstxpcom

DIRS		= public \
		  src \
		  $(NULL)

XPI_NAME 	= firstxpcom

# A Unique ID for your extension
INSTALL_EXTENSION_ID	= firstxpcom@senecac.on.ca

# Will create a .xpi in /mozilla/$(MOZ_OBJDIR)/dist/xpi-stage/
XPI_PKGNAME	= firstxpcom

# install.rdf will tell Firefox how to install our extension and 
# this says, "copy install.rdf into our extension dir and xpi"
DIST_FILES = install.rdf

include $(topsrcdir)/config/rules.mk

Note the DIRS variable. It says that the two directories, public and src, will be entered during the build. Because Mozilla's build system uses recursive make, we also need Makefile.in files in each of these.

mozilla/extensions/firstxpcom/public/Makefile.in

Next we need a Makefile.in the public directory

DEPTH		= ../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

MODULE		= firstxpcom
XPIDL_MODULE	= firstxpcom

XPI_NAME 	= firstxpcom

# The files under EXPORTS are copied directly to
# /mozilla/$(MOZ_OBJDIR)/dist/include/firstxpcom
# and are thus accessible from other modules
#EXPORTS       = \
#		myHeader.h \
#		$(NULL)

XPIDLSRCS	= IFirstXpcom.idl

include $(topsrcdir)/config/rules.mk

Here we tell the build system about our component's name and where its XPIDL file can be found.

mozilla/extensions/firstxpcom/src/Makefile.in

Now a Makefile.in in the src directory:

DEPTH		= ../../..
topsrcdir	= @top_srcdir@
srcdir		= @srcdir@
VPATH		= @srcdir@

include $(DEPTH)/config/autoconf.mk

IS_COMPONENT 	= 1
MODULE 	= firstxpcom
LIBRARY_NAME 	= firstxpcom

XPI_NAME 	= firstxpcom

# The REQUIRES section tells make which modules your 
# components uses. This causes the relevant subdirectories
# of /mozilla/$(MOZ_OBJDIR)/dist/include/ to be added to the
# C++ compiler's include path. If you're including Mozilla
# headers and the compiler isn't finding them, it could well
# mean that you haven't listed all of the necessary modules here.
REQUIRES	= xpcom \
		  string \
		  $(NULL)

# The .cpp source files to be compiled
CPPSRCS	= FirstXpcom.cpp \
		  $(NULL)
include $(topsrcdir)/config/rules.mk

EXTRA_DSO_LDOPTS += \
  $(XPCOM_GLUE_LDOPTS) \
  $(NSPR_LIBS) \
  $(NULL)

mozilla/extensions/firstxpcom/install.rdf

The last build-related file we need to write is a file telling Firefox's addon manager about our extension and how to install it--install.rdf (see http://developer.mozilla.org/en/docs/Install_Manifests for details):

 <?xml version="1.0" encoding="UTF-8"?>
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns:em="http://www.mozilla.org/2004/em-rdf#">
   <Description about="urn:mozilla:install-manifest">
     <em:id>firstxpcom@senecac.on.ca</em:id>
     <em:name>firstxpcom</em:name>
     <em:version>0.1</em:version>
     <em:creator>David Humphrey</em:creator>
     <em:description>A Simple XPCOM Extension.</em:description>
     <em:homepageURL>http://zenit.senecac.on.ca/wiki</em:homepageURL>
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->
         <em:minVersion>2.0</em:minVersion>
         <em:maxVersion>3.0b2pre</em:maxVersion> <!-- trunk build Nov 11, 2007 -->
       </Description>
     </em:targetApplication>
   </Description>
 </RDF>

Building FirstXpcom

Now we're ready to build our component. Add the following line to your .mozconfig file in order to include firstxpcom during the build process:

ac_add_options --enable-extensions=default,firstxpcom

Now you can (BUT DON'T, see below for quick way!!)re-build your tree (also known as "rebuilding world") by doing:

$ cd mozilla
$ make -f client.mk build

BUT...this takes forever for it to actually reach your code, since it has to traverse the entire tree checking for changes. So, you can bypass the rest of the tree and go directly to your component's build.

$ cd $(objdir)
$ ../build/autoconf/make-makefile extensions/firstxpcom

Now call make in $(objdir)/extensions/firstxpcom

$ cd $(objdir)/extensions/firstxpcom
$ make

This will create the public and src directories, as well as generate the Makefiles necessary in each. Go into the public directory and call make again, in order to have your .h and .cpp stubs generated (NOTE: this will cause errors, see below):

$ cd $(objdir)/extensions/firstxpcom/public
$ make

If all goes well, you'll see a hundred lines or so of output from make, ending in an error (NOTE: make will error out, since we have no source files yet). We can safely ignore most of it, but a few lines are significant at this point:

IFirstXpcom.idl
../../../dist/bin/xpidl.exe -m header -w -I../../../../extensions/firstxpcom/public -I../../../dist/idl -o _xpidlgen/IFirstXpcom 

Here we can see the build processing our IDL file. As a result of this first (partially) successful run of make, exported and generated header files (i.e.,from our IDL) will be placed in $(objdir)/dist/include/firstxpcom.

Within $(objdir)/dist/include/firstxpcom/IFirstXpcom.h you'll see the following block of code:

#if 0
/* Use the code below as a template for the implementation class for this interface. */
...
/* End of implementation class template. */
#endif

The code in the middle of this block is what you want to use as the basis for implementing your .cpp file (and .h if you choose to split it out, which we won't). Copy and paste this implementation stub into the following file: mozilla/extensions/firstxpcom/src/FirstXpcom.cpp

You'll need to do a search/replace on _MYCLASS_ and change it to the name of your class, in our case, FirstXpcom.

Now you can try and re-run make for your extension:

$ cd $(objdir)/extensions/firstxpcom
$ make

This will produce a host of errors, mostly related to the fact that we don't have proper includes set-up and it can't find declarations it needs. You need to add an include for your interface's generated .h file:

#include "IFirstXpcom.h"

Re-run make.

As an aside, and while you are thinking about Makefiles, you might take a moment to read bug 371201. This is a bug that was identified while the author was trying to debug his Makefile.in files. Ted Mielczarek (ted on IRC) was finally able to spot the problem--a trailing space on XPI_NAME. I share this anecdote as a way to introduce https://bugzilla.mozilla.org, to emphasize the necessity of the community, and to show the kind of problems that one can have writing files for the build system.--UPDATE: this bug has now been fixed by Ted (March 26, 2007).

Examining IFirstXpcom.h

Let's examine the code more closely and try to make sense of things. Here is the code in $(objdir)/dist/include/firstxpcom/IFirstXpcom.h:

/*
 * DO NOT EDIT.  THIS FILE IS GENERATED FROM c:/temp/proj/ff-trunk/mozilla/obj-fftrunk/extensions/firstxpcom/public/../../../../extensions/firstxpcom/public/IFirstXpcom.idl
 */

#ifndef __gen_IFirstXpcom_h__
#define __gen_IFirstXpcom_h__


#ifndef __gen_nsISupports_h__
#include "nsISupports.h"
#endif

/* For IDL files that don't want to include root IDL files. */
#ifndef NS_NO_VTABLE
#define NS_NO_VTABLE
#endif

/* starting interface:    IFirstXpcom */
#define IFIRSTXPCOM_IID_STR "78af1749-014a-47aa-baec-2669670b7601"

#define IFIRSTXPCOM_IID \
  {0x78af1749, 0x014a, 0x47aa, \
    { 0xba, 0xec, 0x26, 0x69, 0x67, 0x0b, 0x76, 0x01 }}

class NS_NO_VTABLE IFirstXpcom : public nsISupports {
 public: 

  NS_DECLARE_STATIC_IID_ACCESSOR(IFIRSTXPCOM_IID)

  /* attribute AString name; */
  NS_IMETHOD GetName(nsAString & aName) = 0;
  NS_IMETHOD SetName(const nsAString & aName) = 0;

  /* long add (in long a, in long b); */
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;

};

  NS_DEFINE_STATIC_IID_ACCESSOR(IFirstXpcom, IFIRSTXPCOM_IID)

/* Use this macro when declaring classes that implement this interface. */
#define NS_DECL_IFIRSTXPCOM \
  NS_IMETHOD GetName(nsAString & aName); \
  NS_IMETHOD SetName(const nsAString & aName); \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval); 

/* Use this macro to declare functions that forward the behavior of this interface to  another object. */
#define NS_FORWARD_IFIRSTXPCOM(_to) \
  NS_IMETHOD GetName(nsAString & aName) { return _to GetName(aName); } \
  NS_IMETHOD SetName(const nsAString & aName) { return _to SetName(aName); } \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return _to Add(a, b, _retval); } 

/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */
#define NS_FORWARD_SAFE_IFIRSTXPCOM(_to) \
  NS_IMETHOD GetName(nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER :  _to->GetName(aName); } \
  NS_IMETHOD SetName(const nsAString & aName) { return !_to ? NS_ERROR_NULL_POINTER : _to->SetName(aName); } \
  NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) { return !_to ?  NS_ERROR_NULL_POINTER : _to->Add(a, b, _retval); } 

#if 0
/* Use the code below as a template for the implementation class for this interface. */

/* Header file */
class _MYCLASS_ : public IFirstXpcom
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_IFIRSTXPCOM

  _MYCLASS_();

private:
  ~_MYCLASS_();

protected:
  /* additional members */
};

/* Implementation file */
NS_IMPL_ISUPPORTS1(_MYCLASS_, IFirstXpcom)

_MYCLASS_::_MYCLASS_()
{
  /* member initializers and constructor code */
}

_MYCLASS_::~_MYCLASS_()
{
  /* destructor code */
}

/* attribute AString name; */
NS_IMETHODIMP _MYCLASS_::GetName(nsAString & aName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP _MYCLASS_::SetName(const nsAString & aName)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* long add (in long a, in long b); */
NS_IMETHODIMP _MYCLASS_::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* End of implementation class template. */
#endif


#endif /* __gen_IFirstXpcom_h__ */

What things do you notice? What strikes you?

Use of header guards

Header guards (see Include Guards) are a portable technique for ensuring that includes or defines are only done once, for example, that nsISupports.h only be included once:

#ifndef __gen_nsISupports_h__
#include "nsISupports.h"
#endif

Another way of doing this, especially with the Microsoft compiler, is to use

#pragma once

But this is not portable, and therefore Mozilla doesn't use it.

Use of macros

Mozilla provides many convenience macros to help C++ developers do common tasks more efficiently. Much of the code involved in making a module/component is generic, and has to be repeated every time. You can spot macros in the code by the fact that they are all capitals, and often begin with NS_*. Examples include:

NS_DECL_ appended with any interface name in all caps will declare all of the methods of that interface for you (nsIFoo --> NS_DECL_NSIFOO). Looking at the code above shows you how this is possible. For example, NS_DECL_NSIFOO will declare all of the methods of nsIFoo, provided that it exists and that nsIFoo.h was generated by the XPIDL compiler. Consider the following real class:

class myClass : public nsISomeClass
{
  public:
    NS_DECL_ISUPPORTS		// declares AddRef, Release, and QueryInterface
    NS_DECL_NSISOMECLASS	// declares all methods of nsISomeClass 

    myClass();
    virtual ~myClass() {}
};

The declaration of nsISomeClass doesn't include any methods other than the constructor and destructor. Instead, the class uses the NS_DECL_ macro

Also note the use of NS_METHOD and NS_METHODIMP for return type signatures. All XPCOM functions are required to return a result code (nsresult, a integer), which indicates whether or not the function worked (e.g., NS_OK).

Next there is NS_IMPL_ISUPPORTS1. This macro implements the nsISupports interface for you, specifically the implementation of AddRef, Release, and QueryInterface for any object.

NS_IMPL_ISUPPORTS1(classname, interface1)

If your class implements more than one interface, you can simply change the number 1 in the macro to the number of interfaces you support and list the interfaces, separated by commas. For example:

NS_IMPL_ISUPPORTS2(classname, interface1, interface2)
NS_IMPL_ISUPPORTSn(classname, interface1, ..., interfacen)

These macros automatically add the nsISupports entry for you, so you don't need to do the following:

NS_IMPL_ISUPPORTS2(classname, interface1, nsISupports)

As an example, consider mozilla/xpcom/io/nsBinaryStream.cpp:

NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream)

Use of never before-seen types

A quick scan of the code also reveals types that will be unfamiliar, including: nsAString, nsresult, and PRInt32. What are these?

Because Mozilla is cross-platform almost all of the standard types you are used to have Mozilla-specific versions. For example, PRInt32, which is defined as part of the Netscape Portable Runtime (hence the PR prefix), is a signed 32-bit integer, no matter the OS you are using (see http://developer.mozilla.org/en/docs/PRInt32). Depending on the platform you use this could mean a regular int or a long. The same is true of strings (Mozilla has it's own string classes -- see http://developer.mozilla.org/en/docs/XPCOM_string_guide) because of the need for multi-language support and other things necessary to make Mozilla's products work around the world.

At first there are so many of these to learn. But you quickly get accustomed to them, and looking at how other people code (via lxr) can help you in this process.

Strange return types

You'll also notice differences between the original IDL and the autogenerated C++ signatures. In our IDL file, the IFirstXpcom::Add method took two longs and returned a long. However in the C++ code stub it says something different:

 /* XPIDL -- long add (in long a, in long b); */
 NS_IMETHOD Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) = 0;

The return value of XPCOM methods generated from XPIDL is always of the type nsresult, and the small macro used in these expansions, NS_IMETHOD, actually represents nsresult. nsresult is returned even when in XPIDL you specify that the method return a void. If your IDL requires a return type, as ours does, that value will be added as a final parameter to the call list--in this case PRInt32 *_retval.

There are other things you should know about making your code compatible on all of the supported Mozilla platforms. You can read about them here: http://www.mozilla.org/hacking/portable-cpp.html.

Module Code

We now need to write a bit of code to get our component registered. Components reside in modules, and those modules are defined in shared library files (i.e., DLLs or DSOs) that typically sit in the components directory of an XPCOM application.

When you build a component or module and compile it into a library, it must export a single method named NSGetModule. This NSGetModule function is the entry point for accessing the library. It gets called during registration and unregistration of the component, and when XPCOM wants to discover what interfaces or classes the module/library implements.

In addition to implementing the module code (i.e., nsIModule), we also have to write code to allow our component to be created at runtime based on an interface rather than concrete types--essentially, abstracting the process of creation so that clients don't have to know about real classes underneath the interfaces. This means implementing the nsIFactory interface.

Together, these two interfaces will require us to write hundreds lines of code, the majority of which is generic boilerplate code. In order to simplify the work component developers must do, a number of macros help us with this task:

  • NS_GENERIC_FACTORY_CONSTRUCTOR
  • NS_IMPL_NSGETMODULE
#include "nsIGenericFactory.h"
...
// This will result in a function named FirstXpcomConstructor.
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

// 19f3ef5e-759f-49a4-88e3-ed27f9c83011 
#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",
    FIRSTXPCOM_CID,
    "@senecac.on.ca/firstxpcom;1",
    FirstXpcomConstructor
  }
};

NS_IMPL_NSGETMODULE(FirstXpcomModule, components)

First, we have to include nsIGenericFactory.h in order to get NS_GENERIC_FACTORY_CONSTRUCTOR. Now we can add the following line, which will generate a function called FirstXpcomConstructor:

NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

Note: we also could have provided an initialization function to be called after our object gets allocated (i.e., FirstXpcom->Init()):

NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsYourConcreteClassName, Init)

Next, we need to create proper identification for our component's module so that it can be passed to the module implementation macro, NS_IMPL_NSGETMODULE. This macro takes an array of nsModuleComponentInfo so that you can define more than one component per module (remember that a module is a collection of components, and every component belongs to a module so it can get loaded by the system).

Start by generating another uuid, which will be used for identifying our component/class (i.e., we can't re-use our interface's uuid), for example:

19f3ef5e-759f-49a4-88e3-ed27f9c83011

Now write a define to make it easier to pass this Class ID around in C++:

#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

Then we can populate our array with a single entry for the FirstXpcom component:

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",			// descriptive name
    FIRSTXPCOM_CID,			// CID from above
    "@senecac.on.ca/firstxpcom;1",	// Contract ID
    FirstXpcomConstructor		// Factory Constructor
  }
};

The last two entries need some explanation. The Component ID is a human-readable string that clients can use to get/create an instance of your class. We'll see how to do this later on. Here is an example Component ID and what it means:

"@mozilla.org/network/ldap-operation;1"
  • domain = @mozilla.org
  • module = network
  • component = ldap-operation
  • version = 1

The final line, the constructor, is the name of the constructor automatically generated by NS_GENERIC_FACTORY_CONSTRUCTOR. It will be the name of your concrete class followed by "Constructor," in our case FirstXpcomConstructor.

And that's it for the module/factory code.

FirstXpcom.cpp

All that remains is our implementation. Here is the final version of FirstXpcom.cpp (emphasis added to highlight changes):

#include <stdio.h> // for printf()
#include "IFirstXpcom.h" // the CPP .h generated from our .idl
#include "nsIGenericFactory.h" // for NS_GENERIC_FACTORY_CONSTRUCTOR()
#include "nsStringAPI.h" // for nsString

class FirstXpcom : public IFirstXpcom
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_IFIRSTXPCOM

  FirstXpcom();

private:
  ~FirstXpcom();

protected:
  nsString mName;
};

NS_IMPL_ISUPPORTS1(FirstXpcom, IFirstXpcom)

FirstXpcom::FirstXpcom()
{
  /* member initializers and constructor code */
  mName.Assign(NS_LITERAL_STRING("FirstXpcom Component"));
}

FirstXpcom::~FirstXpcom()
{
  /* destructor code */
}

/* attribute AString name; */
NS_IMETHODIMP FirstXpcom::GetName(nsAString & aName)
{
  aName.Assign(mName);
  printf("FirstXpcom::GetName\n");
  return NS_OK;
}

NS_IMETHODIMP FirstXpcom::SetName(const nsAString & aName)
{
  mName.Assign(aName);
  printf("FirstXpcom::SetName\n");
  return NS_OK;
}

/* long add (in long a, in long b); */
NS_IMETHODIMP FirstXpcom::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
  printf("FirstXpcom::Add(%d, %d)", a, b);
  *_retval = a + b;
  return NS_OK;
}

// This will result in a function named FirstXpcomConstructor.
NS_GENERIC_FACTORY_CONSTRUCTOR(FirstXpcom)

// 19f3ef5e-759f-49a4-88e3-ed27f9c83011 
#define FIRSTXPCOM_CID \
  {0x19f3ef5e, 0x759f, 0x49a4, \
      { 0x88, 0xe3, 0xed, 0x27, 0xf9, 0xc8, 0x30, 0x11} }

static const nsModuleComponentInfo components[] =
{
  { "FirstXpcom",
    FIRSTXPCOM_CID,
    "@senecac.on.ca/firstxpcom;1",
    FirstXpcomConstructor
  }
};

NS_IMPL_NSGETMODULE(FirstXpcomModule, components)

Time to call make again:

$ cd $(objdir)/extensions/firstxpcom
$ make

Assuming this works without errors, here's what has happened:

  • Generated makefiles for your project were created in extensions/firstxpcom/ (remember, we're under /mozilla/$(MOZ_OBJDIR)/.
  • Exported header files and generated header files (from IDL) in dist/include/firstxpcom/
  • Static libraries for your modules in dist/lib/ (in case other modules want to link statically instead of using XPCOM).
  • XPI file in dist/xpi-stage/firstxpcom.xpi.
  • Everything else in dist/bin/extensions/firstxpcom@senecac.on.ca

Testing FirstXpcom

Checking the Add-on Manager

We'll write formal tests and code to use our component later. For now, make sure it gets loaded into Firefox and is visible in the Addon Manager. Run Firefox and make sure you can see your extension in the addon manager:

$ cd $(objdir)/dist/bin
$ export MOZ_DEBUG_BREAK=warn
$ firefox.exe -Profilemanager -no-remote

Accessing FirstXpcom from the JavaScript Shell

Now let's try and access this from JavaScript in the browser. If you haven't done so already, download and install the Extension Developer's extension. This will allow you to use the JavaScript Shell inside the browser, making it easy to try out the firstxpcom component.

Launch the JS Shell (Tools > Extension Developer > Javascript Shell) and write some code to access your XPCOM component. You can work interactively without having to define functions, write a complete extension, etc.:

  • Define our component's ID so we can create an instance of it below.
const cid = "@senecac.on.ca/firstxpcom;1"
print(cid)
  • Now create an instance of our component. The value of obj will be nsISupports at this point (i.e., we can't call IFirstXpcom's methods yet).
var obj = Components.classes[cid].createInstance()
  • Next, take the the nsISupports object returned above and query it (i.e., see if it supports your interface type and if so, change to that interface) to IFirstXpcom, often referred to as QI (e.g., "...you need to QI it to IFirstXpcom...").
obj = obj.QueryInterface(Components.interfaces.IFirstXpcom)
  • At this point we have the ability to use the IFirstXpcom methods and attributes:
var sum
sum = obj.add(4,5)

var name
name = "FirstXpcom!"

obj.name = name
print(obj.name)
alert(obj.name)

When you run this code, also notice how your C++ printf statements are causing messages to stdout in your console window (you may need to scrollback through all the other messages to find them).

Resources