Education/Learning/UnderstandingXpcomFiles
Contents
Introduction
One of the first questions that people new to the Mozilla code base have when they start to look at backend source code is how to understand the difference between IDL files and the various C++ files.
This discussion will show the various file types, how they interact, and use a real example. You can read much more about XPCOM in the online book, Creating XPCOM Components. All code samples below are taken from the following revision of mozilla-central: http://hg.mozilla.org/mozilla-central/file/0cd41f599080/
Meet nsILocalFile
What is nsILocalFile?
Let's take as our example the interface, nsILocalFile. The nsILocalFile interface allows us to interact with files on the local file system from C++ or JavaScript code. Before we explore how it's built, we can take a look at its developer documentation and see examples of where it's used in the Firefox source code.
nsILocalFile's Interface
The interface for nsILocalFile is written in nsILocalFile.idl. This is an XPIDL file used to define the interface in a language-independent way. If you look at the source you'll see common elements of XPDIL files, for example:
- That this interface is accessible by JavaScript (i.e., scriptable) and its unique identifier (every IDL has it's own)
61 [scriptable, uuid(aa610f20-a889-11d3-8c81-000064657374)]
- That this interface inherits from another interface (nsIFile, which itself inherits from the most generic interface of all, nsISupports)
62 interface nsILocalFile : nsIFile
- That it has methods.
163 void launch();
- That it has attributes (PRBool is a boolean defined in the Netscape Portable Runtime, which is what PR stands for)
102 attribute PRBool followLinks;
- That it has readonly attributes (PRInt64 is a 64-bit integer defined in the Netscape Portable Runtime, which is what PR stands for)
122 readonly attribute PRInt64 diskSpaceAvailable;
There are other things we could discuss, but this is a good starting point for our look at how this gets implemented, and where.
nsILocalFile.idl vs. nsILocalFile.h
The nsILocalFile.idl file defines the interface, but not in a way useful to the C++ compiler. In order to use it in C++, we need a proper header file. This is where the xpidl compiler comes in. One of the jobs of the build system during the export phase is to create .h files from .idl.
Since the build system generates nsILocalFile.h at compile time, you won't find it in the source tree, nor indexed in MXR. To locate it, you'll need to compile the code yourself, and look in: objdir/xpcom/io/_xpidlgen (substitute your object directory name for objdir). For discussion purposes, here it is: nsILocalFile.h. This explains why you'll see references to nsILocalFile.h throughout the tree (e.g., #include "nsILocalFile.h").
A quick tour of nsILocalFile.h
One of the benefits of the build system automatically generating the header file for us, is that we can focus on writing/reading .idl and leave the dirty work of the .h file to someone else. However, there are some interesting things in there to note.
NS_DECL_NSILOCALFILE
The first one is the macro named NS_DECL_NSILOCALFILE. You'll see macros of this form (i.e., NS_DECL_interface-name) all over the tree. For example, in nsLocalFileWin.h.
83 NS_DECL_NSILOCALFILE
This simplifies the declaration of the members for nsILocalFile, and explains why you won't see them in the source anywhere. Here is what NS_DECL_NSILOCALFILE looks like:
/* Use this macro when declaring classes that implement this interface. */ #define NS_DECL_NSILOCALFILE \ NS_SCRIPTABLE NS_IMETHOD InitWithPath(const nsAString & filePath); \ NS_IMETHOD InitWithNativePath(const nsACString & filePath); \ NS_SCRIPTABLE NS_IMETHOD InitWithFile(nsILocalFile *aFile); \ NS_SCRIPTABLE NS_IMETHOD GetFollowLinks(PRBool *aFollowLinks); \ NS_SCRIPTABLE NS_IMETHOD SetFollowLinks(PRBool aFollowLinks); \ NS_IMETHOD OpenNSPRFileDesc(PRInt32 flags, PRInt32 mode, PRFileDesc * *_retval NS_OUTPARAM); \ NS_IMETHOD OpenANSIFileDesc(const char *mode, FILE * *_retval NS_OUTPARAM); \ NS_IMETHOD Load(PRLibrary * *_retval NS_OUTPARAM); \ NS_SCRIPTABLE NS_IMETHOD GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable); \ NS_SCRIPTABLE NS_IMETHOD AppendRelativePath(const nsAString & relativeFilePath); \ NS_IMETHOD AppendRelativeNativePath(const nsACString & relativeFilePath); \ NS_SCRIPTABLE NS_IMETHOD GetPersistentDescriptor(nsACString & aPersistentDescriptor); \ NS_SCRIPTABLE NS_IMETHOD SetPersistentDescriptor(const nsACString & aPersistentDescriptor); \ NS_SCRIPTABLE NS_IMETHOD Reveal(void); \ NS_SCRIPTABLE NS_IMETHOD Launch(void); \ NS_SCRIPTABLE NS_IMETHOD GetRelativeDescriptor(nsILocalFile *fromFile, nsACString & _retval NS_OUTPARAM); \ NS_SCRIPTABLE NS_IMETHOD SetRelativeDescriptor(nsILocalFile *fromFile, const nsACString & relativeDesc);
nsILocalFile.h Implementation Template
Buried within the generated nsILocalFile.h is a skeleton implementation of the class in C++ that has been ifdef'ed out. This is helpful for people learning the Mozilla source that have to add a feature to an IDL file and then write an implementation. Here is some of what it looks like:
#if 0 /* Use the code below as a template for the implementation class for this interface. */ /* Header file */ class nsLocalFile : public nsILocalFile { public: NS_DECL_ISUPPORTS NS_DECL_NSILOCALFILE nsLocalFile(); private: ~nsLocalFile(); protected: /* additional members */ }; /* Implementation file */ NS_IMPL_ISUPPORTS1(nsLocalFile, nsILocalFile) nsLocalFile::nsLocalFile() { /* member initializers and constructor code */ } nsLocalFile::~nsLocalFile() { /* destructor code */ } /* void initWithPath (in AString filePath); */ NS_IMETHODIMP nsLocalFile::InitWithPath(const nsAString & filePath) { return NS_ERROR_NOT_IMPLEMENTED; } ...
NS_IMETHODIMP and nsresult
If you compare the IDL and .H for the initWithPath method, you'll see some important differences. First, notice that initWithPath has become InitWithPath in the C++ code (it remains initWithPath to JavaScript callers).
Second, notice that where initWithPath returned void in the IDL, we now see the use of the NS_IMETHODIMP macro. XPCOM methods are expected to return a result indicating success or failure, and in the case of failure, a specific error code. This is done using an nsresult, which is an integer value. Many common error cases have macros, for example, NS_OK or NS_ERROR_NOT_IMPLEMENTED. As a result of this, any IDL method that needs to return a value gets an extra argument added for the return value. For example, _retval below:
/* ACString getRelativeDescriptor (in nsILocalFile fromFile); */ NS_IMETHODIMP nsLocalFile::GetRelativeDescriptor(nsILocalFile *fromFile, nsACString & _retval NS_OUTPARAM) { return NS_ERROR_NOT_IMPLEMENTED; }
Getters and Setters
It's also important to notice how IDL attributes get rewritten in C++. Recall the following from nsILocalFile.idl:
102 attribute PRBool followLinks; ... 122 readonly attribute PRInt64 diskSpaceAvailable;
In the first case a readable and writable attribute, and in the second a readonly attribute. Here's how the actual getters and setter look in the generated C++:
/* attribute PRBool followLinks; */ NS_IMETHODIMP nsLocalFile::GetFollowLinks(PRBool *aFollowLinks) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsLocalFile::SetFollowLinks(PRBool aFollowLinks) { return NS_ERROR_NOT_IMPLEMENTED; } ... /* readonly attribute PRInt64 diskSpaceAvailable; */ NS_IMETHODIMP nsLocalFile::GetDiskSpaceAvailable(PRInt64 *aDiskSpaceAvailable) { return NS_ERROR_NOT_IMPLEMENTED; }
The followLinks attribute becomes ::GetFollowLinks and ::SetFollowLinks, and diskSpaceAvailable becomes ::GetDiskSpaceAvailable. Simple when you know, and used everywhere in the source.
nsI vs. ns
The interface is named nsILocalFile and the implementations are all named nsLocalFile*, dropping the I. This is another obvious thing once you know to look for it, but can be confusing when you start and your eye skims over nsIFoo and nsFoo as though they were the same thing. Incidentally, the ns prefix stands for Netscape, and sometimes you'll encounter other naming prefixes such as mozI and moz.
Interface Implementations
Thus far we've looked at how the nsILocalFile.idl interface is defined, and seen what happens when it gets translated into nsILocalFile.h.
Now it's time to look for an implementation of this interface. The relevant files are nsLocalFile.h and nsLocalFileCommon.cpp (note: there are 4 platform-specific files we'll also discuss in a moment).
nsLocalFile.h takes care of including our interface (i.e., the generated header file), and also figures out which platform specific header to include for the implementation:
71 #include "nsILocalFile.h" 72 73 #ifdef XP_WIN 74 #include "nsLocalFileWin.h" 75 #elif defined(XP_MACOSX) 76 #include "nsLocalFileOSX.h" 77 #elif defined(XP_UNIX) || defined(XP_BEOS) 78 #include "nsLocalFileUnix.h" 79 #elif defined(XP_OS2) 80 #include "nsLocalFileOS2.h" 81 #else 82 #error NOT_IMPLEMENTED 83 #endif
The build system once again does the heavy lifting and figures out which implementation to use at compile time (see Makefile.in):
91 ifeq ($(MOZ_WIDGET_TOOLKIT),os2) 92 CPPSRCS += nsLocalFileOS2.cpp 93 else 94 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) 95 CMMSRCS = nsLocalFileOSX.mm 96 else 97 ifeq ($(MOZ_WIDGET_TOOLKIT),windows) 98 CPPSRCS += nsLocalFileWin.cpp 99 else 100 CPPSRCS += nsLocalFileUnix.cpp 101 endif # windows 102 endif # mac 103 endif # OS2
nsLocalFileCommon.cpp includes nsLocalFile.h, and therefore all the platform specific headers we added above. It implements some common elements of the interface, leaving specifics to platform-specific implementations. If we take Windows and nsLocalFileWin.h as an example, we see where the NS_DECL_NSILOCALFILE macro gets used, and therefore where our interface gets declared:
82 // nsILocalFile interface 83 NS_DECL_NSILOCALFILE
We also see how our interface methods and attributes get implemented for this platform. Here is the code for the followLinks attribute and launch method:
2565 /* attribute PRBool followLinks; */ 2566 NS_IMETHODIMP 2567 nsLocalFile::GetFollowLinks(PRBool *aFollowLinks) 2568 { 2569 *aFollowLinks = mFollowSymlinks; 2570 return NS_OK; 2571 } 2572 NS_IMETHODIMP 2573 nsLocalFile::SetFollowLinks(PRBool aFollowLinks) 2574 { 2575 MakeDirty(); 2576 mFollowSymlinks = aFollowLinks; 2577 return NS_OK; 2578 } ... 2678 NS_IMETHODIMP 2679 nsLocalFile::Launch() 2680 { 2681 const nsString &path = mWorkingPath; 2682 2683 // use the app registry name to launch a shell execute.... 2684 LONG r = (LONG) ::ShellExecuteW(NULL, NULL, path.get(), NULL, NULL, 2685 SW_SHOWNORMAL); 2686 2687 // if the file has no association, we launch windows' "what do you want to do" dialog 2688 if (r == SE_ERR_NOASSOC) { 2689 nsAutoString shellArg; 2690 shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") + path); 2691 r = (LONG) ::ShellExecuteW(NULL, NULL, L"RUNDLL32.EXE", shellArg.get(), 2692 NULL, SW_SHOWNORMAL); 2693 } 2694 if (r < 32) { 2695 switch (r) { 2696 case 0: 2697 case SE_ERR_OOM: 2698 return NS_ERROR_OUT_OF_MEMORY; 2699 case ERROR_FILE_NOT_FOUND: 2700 return NS_ERROR_FILE_NOT_FOUND; 2701 case ERROR_PATH_NOT_FOUND: 2702 return NS_ERROR_FILE_UNRECOGNIZED_PATH; 2703 case ERROR_BAD_FORMAT: 2704 return NS_ERROR_FILE_CORRUPTED; 2705 case SE_ERR_ACCESSDENIED: 2706 return NS_ERROR_FILE_ACCESS_DENIED; 2707 case SE_ERR_ASSOCINCOMPLETE: 2708 case SE_ERR_NOASSOC: 2709 return NS_ERROR_UNEXPECTED; 2710 case SE_ERR_DDEBUSY: 2711 case SE_ERR_DDEFAIL: 2712 case SE_ERR_DDETIMEOUT: 2713 return NS_ERROR_NOT_AVAILABLE; 2714 case SE_ERR_DLLNOTFOUND: 2715 return NS_ERROR_FAILURE; 2716 case SE_ERR_SHARE: 2717 return NS_ERROR_FILE_IS_LOCKED; 2718 default: 2719 return NS_ERROR_FILE_EXECUTION_FAILED; 2720 } 2721 } 2722 return NS_OK; 2723 }
Conclusion
We've followed the chain from our IDL to a C++ implementation specific to Windows, in the processing visiting:
- nsILocalFile.idl
- nsILocalFile.h
- nsLocalFile.h
- nsLocalFileWin.h
- nsLocalFileCommon.cpp
- nsLocalFileWin.cpp
Understanding the difference between all these files, and knowing where to look for them, goes a long way to helping you understand the code.
For further information about how to use XPCOM interfaces like nsILocalFile, see Using XPCOM Components in C++ and JavaScript.