Education/Learning/UnderstandingXpcomFiles

From MozillaWiki
Jump to: navigation, search

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.