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:
- app_repo
- app
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:
- app_repo
- app
- xulrunner
- MacApp.app
- Contents
- Frameworks
- XUL.framework -> /Library/Frameworks/XUL.framework
- Info.plist
- MacOS
- xulrunner -> /Library/Frameworks/XUL.framework/Versions/Current/xulrunner Resources -> ../../app
- Frameworks
- Contents
- app
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
.
- Windows and PPC Mac: http://developer.mozilla.org/en/docs/Gecko_SDK
- Intel Mac: http://www.oxymoronical.com/view/1114
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)
Wowza. Way over my head!
— Dimitry — 2007/07/17 6:35 pm
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
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
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
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
[... 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
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
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