Home > Building a C++ XPCOM Component in Windows

Building a C++ XPCOM Component in Windows

February 7th, 2010

I’ve been teaching myself to write Firefox extensions for the last few weeks, and became interested in XPCOM components. Unfortunately, I couldn’t find a good (and recent) summary of them, and had to spend 3 or 4 days cobbling together various tutorials, so I figured it’s time to write one.

What is XPCOM?

XPCOM is a language-agnostic communication platform used in Mozilla products (and some other random pieces of software) to allow code (specifically extensions) to be written in a wide variety of languages.

Why would I want to use XPCOM?

There are two ways to “use” XPCOM. First, you can call functions through XPCOM. For example, the Firefox bookmarks service uses an XPCOM interface. So in order to interact with this service from Javascript you would do something like:

var bmarks = Components.classes["@mozilla.org/browser/bookmarks-service;1"].getService();
bmarks.QueryInterface(Components.interfaces.nsIBookmarksService);
bmarks.addBookmarkImmediately("http://www.mozilla.org","Mozilla",0,null);

There are plenty of tutorials on doing this as it is the more common use for XPCOM, so I won’t go into any detail on it here.

The second way is to write an XPCOM service. That is what this tutorial covers. Sometimes you need extra functionality, speed, or just want to tie into some library that requires a different language. Most commonly this is C++, but there is also JavaXPCOM and PyXPCOM (and probably a few others). I’ll be talking about C++, since it’s what I needed for my project.

Warnings

  1. Before you trudge through this: you most likely are in the wrong place. Firefox extensions are usually all Javascript. If you can use JS to do what you want, stop now. There is no need to go through the complexity of an XPCOM component when you can just use JS. Go read a tutorial about writing extensions and get to work.
  2. There is something called ctypes coming to FF 3.7 that may make doing this a lot easier. I haven’t touched this at all, but it may be worth considering if you can wait for the functionality and only need to tie into a particular DLL for some functionality. Long story short, XPCOM may become the more difficult way to call C++ functions from FF extensions.

My Setup

  • Windows 7
  • Visual C++ Express 2008 (free from Microsoft’s website)
  • Firefox 3.6 (Gecko 1.9.2)
  • A UUID or GUID generator. This is a unique (read: random) ID that identifies your app to the world. Windows and Linux have tools to generate this (guidgen & uuidgen, respectively), or you can find various online generators (Mozilla links to several). I recommend this one since it gives you the C++ encoded form too, which you will need. You need two different UUIDs: one for your interface and one for your component.
  • Ability to read and understand C++

Sample Code

If you don’t want to go through the tutorial and just want everything to work, then download this sample code. Just follow step #1 of the tutorial, then make sure your Gecko SDK directory is set right in the build step, and you can breeze on by most of this article.

The Tutorial

This is mostly paraphrased from Alex Sirota’s great tutorial, but it hasn’t been updated since 2005 and is a bit outdated. This new one should work out of the box for FF 3.6.

This tutorial will create a component called MyComponent with one function: Add, which will take 2 numbers and, surprisingly, return the sum.

  1. Download the Gecko SDK for your version of Firefox. I used 1.9.2 for FF 3.6.
  2. Create an idl file - IMyComponent.idl, with the following (replacing ***IUID*** with your interface UUID):
    #include "nsISupports.idl"
    
    [scriptable, uuid(***IUID***)]
    interface IMyComponent : nsISupports
    {
      long Add(in long a, in long b);
    };
    

    This file is a language-agnostic interface definition which you can read more about here.

  3. Generate the interface header and typelib files out of the interface definition file. Assuming you extracted the Gecko SDK to C:\xulrunner-sdk\, run the following commands (from the directory you saved IMyComponent.idl to):
    C:\xulrunner-sdk\sdk\bin\xpidl.exe -m header -I C:\xulrunner-sdk\idl .\IMyComponent.idl
    C:\xulrunner-sdk\sdk\bin\xpidl.exe -m typelib -I C:\xulrunner-sdk\idl .\IMyComponent.idl
    

    These will create IMyComponent.h and IMyComponent.xpt, respectively.

  4. IMyComponent.h has two snippits of code that you can use for the next two files. Everything between /* Header file */ and /* Implementation file */ can be used for MyComponent.h:
    1. Start by inserting double inclusion protection code and the right include:
      #ifndef _MY_COMPONENT_H_
      #define _MY_COMPONENT_H_
      
      #include "IMyComponent.h"
    2. Add the following lines, which define your component name, contract ID, and CUID (where ***CUID*** is the C++-style component UUID, of the form { 0×12345678, 0×9abc, 0xdef0, { 0×12, 0×34, 0×56, 0×78, 0×9a, 0xbc, 0xde, 0xf0 } }):
      #define MY_COMPONENT_CONTRACTID "@example.com/XPCOMSample/MyComponent;1"
      #define MY_COMPONENT_CLASSNAME "A Simple XPCOM Sample"
      #define MY_COMPONENT_CID ***CUID***
    3. Copy in the snippet from IMyComponent.h, replacing all the instances of _MYCLASS_ with the name of your component (MyComponent).
    4. Finish off the double inclusion protection code with #endif //_MY_COMPONENT_H_
  5. Everything between /* Implementation file */ and /* End of implementation class template. */ can be used for MyComponent.cpp:
    1. Start by inserting the right include:
      #include "MyComponent.h"
    2. Copy in the snippet from IMyComponent.h, replacing all the instances of _MYCLASS_ with the name of your component (MyComponent).
    3. Add some implementation to the Add method. I replaced return NS_ERROR_NOT_IMPLEMENTED; with
      	*_retval = a + b;
      return NS_OK;
  6. Create your module definitions files:
    #include "nsIGenericFactory.h"
    #include "MyComponent.h"
    
    NS_GENERIC_FACTORY_CONSTRUCTOR(MyComponent)
    
    static nsModuleComponentInfo components[] =
    {
        {
           MY_COMPONENT_CLASSNAME,
           MY_COMPONENT_CID,
           MY_COMPONENT_CONTRACTID,
           MyComponentConstructor,
        }
    };
    
    NS_IMPL_NSGETMODULE("MyComponentsModule", components) 

You now have all of the files needed to build an XPCOM component:

IMyComponent.h
IMyComponent.idl
IMyComponent.xpt
MyComponent.cpp
MyComponent.h
MyComponentModule.cpp

Now comes the hard part: getting the damn thing to build.

Building the code

Ok, it’s actually not hard since I’ve done most of the legwork for you. Assuming you’re using Visual C++ 2008 here are the settings you need (again assuming C:\xulrunner-sdk is where your Gecko SDK is). In Project->Properties:

Configuration Properties
  General
    Configuration Type: .dll
  C/C++
    General
      Additional Include Directories: C:\xulrunner-sdk\include
    Preprocessor
      Preprocessor Definitions: XP_WIN;XP_WIN32;XPCOM_GLUE_USE_NSPR
  Linker
    General
      Additional Library Directories: C:\xulrunner-sdk\lib
    Input
      Additional Dependencies: nspr4.lib xpcom.lib xpcomglue_s.lib

If you put the idl file into your project, be sure to mark it “Excluded from Build” in its properties…we don’t want VS touching it.

Cross your fingers, pray to whatever deity you believe in, and hit the build button. If it didn’t work let me know why in the comments and I’ll try to build a troubleshooting section.

Installing/Testing the Code

  1. Copy two files to C:\Program Files\Mozilla Firefox\components:
    • The DLL the build generated
    • IMyComponent.xpt
  2. Normally, if this was installed as part of an extension, it would automatically search this directory and find these files. But now we have to force a refresh. Delete xpti.dat. and compreg.dat from your profile directory (FF will regenerate them on next restart)
  3. Close Firefox and open it with this test file (MyComponentTest.html in the sample code):
    <html>
    <script type="text/javascript">
    function MyComponentTestGo() {
    	try {
    		// normally Firefox extensions implicitly have XPCOM privileges, but since this is a file we have to request it.
    		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
    		const cid = "@example.com/XPCOMSample/MyComponent;1";
    		obj = Components.classes[cid].createInstance();
    		// bind the instance we just created to our interface
    		obj = obj.QueryInterface(Components.interfaces.IMyComponent);
    	} catch (err) {
    		alert(err);
    		return;
    	}
    	var res = obj.Add(3, 4);
    	alert('Performing 3+4. Returned ' + res + '.');
    }
    </script>
    <body>
    <button onclick="MyComponentTestGo();">Go</button>
    </body>
    </html>
  4. One last time: cross your fingers, pray to whatever deity you believe in, and hit the Go button. If it didn’t work let me know why in the comments and I’ll try to build a troubleshooting section.

Hopefully this clears up what looked like a lot of confusion to me. I will keep this updated to the best of my abilities and hopefully it will continue to be useful for a long time.

, ,

  1. jgershen
    February 7th, 2010 at 17:25 | #1

    Some more warnings might be appropriate -

    #3 - If you just got here from a search engine and this is your first time on the site, take note. Brian is extremely good at what he does, and while he doesn’t really make this sound easy, it’s still harder than it looks. The guy worked for Mozilla (admittedly not on this) and does webdev professionally. My point is, just keep in mind that getting XPCOM to work is absolutely nontrivial.

    #4 - If you’re trying to get XPCOM to work on OS X… well, honestly, you should really try to think about another way of accomplishing your goal. Before I committed to compiling an XPCOM component on a Mac, I would wait and see if ctypes magically solved all of my problems.

    TL;DR - its harder than it looks.

  2. February 8th, 2010 at 08:45 | #2

    Your blogging software doesn’t know the difference between 0x12 (U+0078 Latin Small Letter X) and 0×12 (U+00D7 Multiplication Sign).

  3. Appa
    February 9th, 2010 at 09:18 | #3

    Any hope you could describe the compiling of this on linux? Thanks.

  4. February 10th, 2010 at 01:12 | #4

    @Appa: Not incredibly high on my TODO list, but I may eventually toy with it if I go back to a Linux desktop.

  5. Jesse Jackman
    February 25th, 2010 at 03:38 | #5

    Thanks for the post it was a huge help

  6. gil
    February 25th, 2010 at 21:12 | #6

    Hello,
    Thanks, I compiled it on linux with the makefile at the end of this page:
    http://groups.google.fr/group/mozilla.dev.tech.xpcom/tree/browse_frm/month/2008-12/58e0638e17596c16?rnum=1&_done=%2Fgroup%2Fmozilla.dev.tech.xpcom%2Fbrowse_frm%2Fmonth%2F2008-12%3F
    I ontained a “.so”
    I did all the end of the procedure but when I went on the htmlpage and I click on the button I get :
    “TypeError: Components.classes[cid] is undefined”
    so bad for me…
    I have firefox 3.5.8, i have compiled with sdk 1.9.1.7 and 1.9.2, same result…

  7. February 26th, 2010 at 22:56 | #7

    @gil: Did you copy the .xpt file and the .so file into the right folder? That error means that it’s not actually loading your component, so either you’re referencing the component wrong (wrong name), it didn’t build correctly, or you didn’t put it in the right place.

    Let me know if that helps.

  8. gil
    February 27th, 2010 at 03:55 | #8

    Yes I put them in good directory, (delete xpti.dat and compreg.dat)
    when restart Firefox, in tools–>error concsole, I’ve got this 3 errors :
    “Failed to load XPCOM component: /usr/lib/firefox-3.5.8/components/MyComponent.so”
    “Failed to load XPCOM component: /usr/lib/xulrunner-1.9.1.8/components/pyabout.py”
    “Failed to load XPCOM component: /usr/lib/xulrunner-1.9.1.8/components/py_test_component.py”

    I’ve tried too an install with the xpi method and it looks similar errors in the console…
    I make an install.rdf and a folder “components” that include the “.so” and the “.xpt”,then I pack them all in a “xpi”, is it equivalent to the first method?

  9. gil
    February 27th, 2010 at 17:39 | #9

    I’ve tested your tuto on windows and it works very well, thanks !
    even with the xpi method
    the problem is only on linux, so pgerhaps because I am on FF 3.5.8 on linux or my compilation script isn’t good…

    I’ll continue to devellop on windows waiting to find a solution on linux
    here’s the result of the command :
    ./run-mozilla.sh `which ldd` -r /usr/lib/firefox-3.5.8/components/MyComponent.so
    undefined symbol: _Z16NS_TableDrivenQIPvPK12QITableEntryRK4nsIDPS_ (/usr/lib/firefox-3.5.8/components/MyComponent.so)
    undefined symbol: _Z20NS_NewGenericModule2PK12nsModuleInfoPP9nsIModule (/usr/lib/firefox-3.5.8/components/MyComponent.so)
    linux-gate.so.1 => (0×00d85000)
    libnspr4.so => /usr/lib/libnspr4.so (0×00f94000)
    libplds4.so => /usr/lib/libplds4.so (0×00e17000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0×00110000)
    libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0×00546000)
    libgcc_s.so.1 => /lib/libgcc_s.so.1 (0×00a8c000)
    libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0×00612000)
    libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0×0099a000)
    libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0×00ec5000)
    /lib/ld-linux.so.2 (0×00225000)

  1. No trackbacks yet.