Crowley Code! 
 (Take 12)

Cross platform XPCOM (a howto) 2007/07/17

The most important part of Mozilla's XPCOM technology is its promise of a cross-platform interface between Javascript and native code.  Yet few examples exist showing how to build a codebase for both Windows and Mac OS X.  Building on Mark Finkle's Windows how-to, here is a completion showing the parallel track for Mac OS X and how to setup your development environment for a code-once, compile-twice, use anywhere XPCOM build.

Our entire app is going to reside within the app_repo directory.  Really you can place it wherever you like, but remember to change paths appropriately.  You know the drill.  Confusingly, the root of our app will be the app subdirectory.  So far we have:

XULRunner

Since the goal here is an XPCOM object for use in XULRunner, we'll start there.  Installing XULRunner is quite painless.  We have a version for each platform, which are kept quite separate.

XULRunner for Windows is simply a ZIP file, which you should extract into the root of your app directory.  Copy the xulrunner-stub.exe program from app/xulrunner down into app and rename it however you like.  This stub just looks for a XULRunner app in the current directory and runs it.  Handy.

XULRunner for Mac is slightly more complicated because of strict requirements for GUI apps running in OS X.  First, go download XULRunner and install the package.  It will create itself deep within /Library/Frameworks (quite separate from the Windows version).  In app_repo, create a new directory called MacApp.app or something else ending in .app.  Within this directory create one called Contents (capitalization is important), and within Contents create Frameworks and MacOS.  Now create three symbolic links to complete the Mac directory structure:

ln -s /Library/Frameworks/XUL.framework MacApp.app/Contents/Frameworks/XUL.framework
ln -s ../../app MacApp.app/Contents/Resources
ln -s /Library/Frameworks/XUL.framework/Versions/Current/xulrunner MacApp.app/Contents/MacOS/xulrunner

Now create MacApp.app/Contents/Info.plist and dump this in, making sure to change things in ALL CAPS.  I am almost certain this is not optimal as it repeats itself a lot.  But it is functional.  Can someone who knows demystify this?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>CFBundleDevelopmentRegion</key>
        <string>English</string>
        <key>CFBundleExecutable</key>
        <string>xulrunner</string>
        <key>CFBundleGetInfoString</key>
        <string>3.0</string>
        <key>CFBundleIdentifier</key>
        <string>YOUR_APP_ID</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>YOUR_APP_VERSION</string>
        <key>CFBundleName</key>
        <string>YOUR_APP_NAME</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
        <string>YOUR_APP_VERSION</string>
        <key>CFBundleSignature</key>
        <string>YOUR_APP_ID</string>
        <key>CFBundleURLTypes</key>
        <array>
                <dict>
                        <key>CFBundleURLName</key>
                        <string>YOUR_APP_NAME</string>
                        <key>CFBundleURLSchemes</key>
                        <array>
                                <string>chrome</string>
                        </array>
                </dict>
        </array>
        <key>CFBundleVersion</key>
        <string>YOUR_APP_VERSION</string>
</dict>
</plist>

As a review, here's how our tree looks now:

The rest of your XULRunner app

Building the XUL and Javascript part of a XULRunner app is a bit outside of the scope of this already very long post.  There are plenty of good references on this though, starting with XUL Planet.  Go forth and learn.

The Gecko SDK

You'll need to grab the Gecko SDK to build XPCOM objects.  I took these and placed them in app at gecko-sdk.win and gecko-sdk.mac.  I haven't started building PPC Mac versions, but the SDK for those will be under gecko-sdk.ppc.

At last, some C++

Now that your development environment is setup, I'll let you write some C++ code.  I created another directory within app called xpcom, in which I placed my project, called foo.  Here are some stub files that will get you off the ground.  Be sure to create your own UUID.

foo.idl

#include "nsISupports.idl"
[scriptable, uuid(0e0d0b74-2c06-11dc-8314-0800200c9a66)]
interface IFoo : nsISupports {
	attribute AString name;
	long add(in long a, in long b);
};

foo.cpp (just to make Windows happy)

#include "windows.h"
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
	return TRUE;
}

foo_impl.h

#ifndef FOO_IMPL_H
#define FOO_IMPL_H
#include "foo.h"
#include "nsStringAPI.h"
#define FOO_CONTRACTID "@richarddcrowley.org/foo;1"
#define FOO_CLASSNAME "Foo"
#define FOO_CID { 0x0e0d0b74, 0x2c06, 0x11dc, { 0x83, 0x14, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
class CFoo : public IFoo {
public:
	NS_DECL_ISUPPORTS
	NS_DECL_IFOO
	CFoo();
private:
	~CFoo();
protected:
	nsString mName;
};
#endif

foo_impl.cpp

#include "foo_impl.h"
NS_IMPL_ISUPPORTS1(CFoo, IFoo)
CFoo::CFoo() {
}
CFoo::~CFoo() {
}
NS_IMETHODIMP CFoo::GetName(nsAString & aName) {
	aName.Assign(mName);
	return NS_OK;
}
NS_IMETHODIMP CFoo::SetName(const nsAString & aName) {
	mName.Assign(aName);
	return NS_OK;
}
NS_IMETHODIMP CFoo::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval) {
	*_retval = a + b;
	return NS_OK;
}

foo_module.cpp

#include "nsIGenericFactory.h"
#include "foo_impl.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(CFoo)
static nsModuleComponentInfo components[] = {
	{
		FOO_CLASSNAME,
		FOO_CID,
		FOO_CONTRACTID,
		CFooConstructor,
	}
};
NS_IMPL_NSGETMODULE("FooModule", components)

Building for Windows

Because the length here is getting out of hand, I will defer to Mark Finkle for the ins-and-outs of the Visual Studio build.  The short version is you setup a few includes and libraries, set some preprocessor directives, and go.  Mark Finkle's Weblog » Mozilla Platform - XPCOM in C++

I used Visual Studio 2005 on Windows XP and did exactly as Mark prescribed.  It worked on the first try.  I wanted to gloat a little bit, but I seriously hope you have as much luck.

Building for Mac

Building on the Mac is easy since a Mac is UNIX-y.  Use this Makefile:

GECKO_SDK := ../../gecko-sdk.mac
XULRUNNER := /Library/Frameworks/XUL.framework/Versions/Current
DEFINE := -DXP_UNIX -DXP_MACOSX
all: xpt dylib
xpt:
	$(GECKO_SDK)/bin/xpidl -m header -I$(GECKO_SDK)/idl foo.idl
	$(GECKO_SDK)/bin/xpidl -m typelib -I$(GECKO_SDK)/idl foo.idl
impl:
	g++ -w -c -o foo_impl.  -I $(GECKO_SDK)/include $(DEFINE) foo_impl.cpp
module:
	g++ -w -c -o foo_module.  -I $(GECKO_SDK)/include $(DEFINE) foo_module.cpp
dylib: impl module
	g++ -dynamiclib -o foo.dylib foo_impl.  foo_module.  -L$(GECKO_SDK)/lib
	-L$(XULRUNNER) -Wl,-executable_path,$(XULRUNNER)
	-lxpcomglue_s -lxpcom -lnspr4
clean:
	rm *.o
	rm foo.h
	rm foo.xpt
	rm foo.dylib

Having the XPT file (which is the same for all platforms) as well as a DLL and a DYLIB, we can dump them into app/components, increment BuildID in application.ini and run our application.

The interface between C++ and Javascript

Now that the application has access to the native code, let's write some Javascript to call it.  The code here will create the object, alerting us of any error, and then use the add function we defined in foo_impl.cpp.

try {
	var xpcom = Components.classes['@richarddcrowley.org/foo;1'].createInstance();
	foo = xpcom.QueryInterface(Components.interfaces.IFoo);
} catch (err) alert(err);
alert(foo.add(3, 4));

That wasn't so bad, was it?

This has been a bit of a whirlwind tour, I know.  But that's how it goes when you're playing with relatively new technologies.  It will be sweet, though, the first time you get 7 from 3 + 4.

Comments (10)

  1. Wowza. Way over my head!

    Dimitry — 2007/07/17 6:35 pm

  2. Just wanted you to know this helped me out. The app name for the third symlink is wrong though ;)

    Isaac — 2007/10/06 2:45 pm

  3. Richard, thanks for the sample files. The problem I am facing is generating the xpt file from idl. Using XPIDL utilty results in problem with finding MSVCR80.dll(Iam using MSVS 2005 for development). Is there any way so that I can bind the SDK( the whole include, lib, idl and bin ) to MSVS 2005. I can't generate xpt file without which anything coded will be worth nothing. Please help me out. Send me in my email id, some workarounds. I know you maybe busy with your activities, but it is you people who can guide people like us.

    — Anil — 2007/10/10 10:58 pm

  4. It sounds like you're using the Microsoft IDL compiler instead of Mozilla's.  The two are incompatible so here is what I'd recommend.  In the directory with all of your components create xpidl-build.bat and put these two lines in it:

    "..\gecko-sdk.win\bin\xpidl.exe" -m header -I"..\gecko-sdk.win\idl" %1 "..\gecko-sdk.win\bin\xpidl.exe" -m typelib -I"..\gecko-sdk.win\idl" %1

    Then run xpidl-build.bat with the argument gm.idl to build gm.  and gm.xpt.  You can setup a custom build step in Visual Studio to run this, or you can just manually run it every time you change your IDL file (it's pointless otherwise).

    I do not know much about building the whole SDK.  I haven't yet needed that much control so I haven't dug in.  If you do decide to, link me some instructions.  Good luck!

    Richard Crowley — 2007/10/11 5:44 am

  5. Hello. I'm from Korea.

    So, I can't speak English well.

    Now, I want to make Mozilla firefox plugins on mac.

    So I try to do this post.

    But, I have some link error. ( I'm a Newbie on mac. )

    My error message is that :

    id: Undefined symbols: _NS_StringContainerFinish _NS_StringContainerInit _NS_StringCopy __Z20NS_NewGenericModule2PK12nsModuleInfoPP9nslModule /usr/bin/libtool: internal link edit command failed make: *** [dylib] Error 1

    ----------------------------------------------------------------

    Can I get your help?

    — junpp — 2007/12/13 3:49 am

  6. [...  http://groups.google.pt/group/mozilla.dev.tech.xul/browse_thread/thread/d8c0127036615492 http://rcrowley.org/2007/07/17/cross-platform-xpcom-a-howto/ [...]

    Paradise Road: Ken’s Blog » Blog Archive » Creating a XULRunner 1.9 App on OS X — 2008/04/29 11:18 am

  7. I think this has helped me as well! I've done everything up to the last point where the IFoo object is created in JavaScript. In my case, I want to create it in C++ and return it as a callback parm to JavaScript. I have this code:

    nsCOMPtr myFoo = do_CreateInstance(FOO_CONTRACTID);

    but myFoo returns as NULL. Looking at the JavaScript, I'm not sure what it is do_CreateInstance is returning if the JavaScript object you use (var xpcom) needs to QI for IFoo. Any ideas?

    Good stuff...  Thanks!

    Kenn — 2008/05/15 11:48 am

  8. This is what I'm looking for. Can I ask the author or anyone who has followed this how to, and successfully completing the application, to put a download link or upload a file? This is for ease of use for new users like me. I'll do it myself when I'm done with it, if no one has beat me to it.

    AC

    — Aaron Calderon — 2008/11/14 12:55 pm

Richard Crowley?  Kentuckian engineer who cooks and eats in between bicycling and beering.

I blog mostly about programming and databases.  Browse by month or tag.

To blame for...


© 2009 Richard Crowley.  Managed by Bashpress.