Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

String Handling in XPCOM

4.78/5 (8 votes)
16 Aug 2010CPOL7 min read 52.2K   315  
Using different types of strings in XPCOM.

Purpose of this Article

I have written this tutorial for programmers who have some experience with XPCOM and can create XPCOM components in C++ and now want to know how to handle strings. The article briefly covers string manipulation with XPCOM objects, and how to use strings in XPCOM clients using VC 8.0. This article does not cover XPCOM basics or concepts.

Introduction

XPCOM is a cross platform component object model, similar to Microsoft COM. It has multiple language bindings, letting XPCOM components be used and implemented in JavaScript, Java, and Python, in addition to C++. Interfaces in XPCOM are defined in a dialect of IDL called XPIDL.

In this article, I present the guidelines for creating simple XPCOM objects from first principles. The components should be usable by both VC++/ JavaScript clients.

As an exercise, we will attempt to design an XPCOM component that will implement the different types of strings to reverse it. The component must take in a parameter and return the reversed string to the user.

Different Types of Strings in XPCOM Components

Naming convention for wide and narrow string classes:

WideNarrow
nsAStringnsACString
nsStringnsCString
nsAutoStringnsCAutoString
etc...

Strings can be stored in two basic formats: 8-bit code unit (byte/char) strings, or 16-bit code unit (PRUnichar) strings. Any string class with a capital "C" in the class name contains 8-bit bytes. These classes include nsCString, nsDependentCString, and so forth. Any string class without the "C" contains 16-bit code units.

The Abstract Classes

Every string class derives from nsAString/nsACString. This class provides the fundamental interface for access and manipulation of strings. While concrete classes derive from nsAString, nsAString itself cannot be instantiated.

This is very similar to the idea of an "interface" that Mozilla uses to describe abstract object descriptions in the rest of the codebase. In the case of interfaces, class names begin with "nsI" where "I" refers to "Interface". In the case of strings, abstract classes begin with "nsA" and the "A" means "Abstract".

There are a number of abstract classes which derive from nsAString. These abstract subclasses also cannot be instantiated, but they describe a string in slightly more detail than nsAString. They guarantee that the underlying implementation behind the abstract class provides specific capabilities above and beyond nsAString.

The list below describes the main base classes. Once you are familiar with them, see the appendix describing What Class to Use When.

  • nsAString/nsACString: the abstract base class for all strings. It provides an API for assignment, individual character access, basic manipulation of characters in the string, and string comparison. This class corresponds to the XPIDL AString parameter type. nsAString is not necessarily null-terminated.
  • nsString/nsCString: builds on nsAString by guaranteeing a null-terminated storage. This allows for a method (.get()) to access the underlying character buffer. (For backwards compatibility, nsAFlatString is a typedef for this string class.)

The remainder of the string classes inherit from either nsAString or nsString. Thus, every string class is compatible with nsAString.

The Concrete Classes - Which Classes to Use When

The concrete classes are for use in code that actually needs to store string data. The most common uses of the concrete classes are as local variables, and members in classes or structs.

nsAString_internal-graph.png

The following is a list of the most common concrete classes. Once you are familiar with them, see the appendix describing What Class to Use When.

  • nsString / nsCString - a null-terminated string whose buffer is allocated on the heap. Destroys its buffer when the string object goes away.
  • nsAutoString / nsCAutoString - derived from nsString, a string which owns a 64 code unit buffer in the same storage space as the string itself. If a string less than 64 code units is assigned to an nsAutoString, then no extra storage will be allocated. For larger strings, a new buffer is allocated on the heap.
  • nsXPIDLString / nsXPIDLCString - derived from nsString; this class supports the getter_Copies() operator which allows easy access to XPIDL out wstring / string parameters. This class also supports the notion of a null-valued buffer, whereas nsString's buffer is never null.
  • nsDependentString - derived from nsString; this string does not own its buffer. It is useful for converting a raw string (const PRUnichar* or const char*) into a class of type nsAString. Note that you must null-terminate buffers used by nsDependentString. If you don't want to or can't null-terminate the buffer, use nsDependentSubstring.
  • nsPrintfCString - derived from nsCString; this string behaves like an nsCAutoString. The constructor takes parameters which allows it to construct an 8-bit string from a printf-style format string and parameter list.
  • NS_LITERAL_STRING/NS_NAMED_LITERAL_STRING - these convert a literal string (such as "abc") to an nsString or a subclass of nsString. On platforms supporting double-byte string literals (e.g., MS VC++ or GCC with the -fshort-wchar option), these are simply macros around the nsDependentString class. They are slightly faster than just wrapping them with an nsDependentString because they use the compiler to calculate their length, and they also hide the messy cross-platform details of non-byte literal strings.

There are also a number of concrete classes that are created as a side-effect of helper routines, etc. You should avoid direct use of these classes. Let the string library create the class for you.

ContextClassNotes
Local Variables

nsAutoString

nsCAutoString

 
Class Member Variables

nsString

nsCString

 
Method Parameter types

nsAString

nsACString

Uses abstract classes for parameters. Uses const nsAString& for "in" parameters and nsAString& for "out" parameters.
Retrieving "out" string/wstrings

nsXPIDLString

nsXPIDLCString

Uses getter_Copies(). Similar to nsString / nsCString.
Wrapping character buffers

nsDependentString

nsDependentCString

Wraps const char* / const PRUnichar* buffers.
Literal strings

NS_LITERAL_STRING

NS_LITERAL_CSTRING

Similar to nsDependent[C]String, but pre-calculates length at build time.

IDL String Types

The string library is also available through IDL. By declaring attributes and methods using the specially defined IDL types, string classes are used as parameters to the corresponding methods.

IDL typeC++ TypePurpose
stringchar*Raw character pointer to ASCII (7-bit) string, no string classes used. High bit is not guaranteed across XPConnect boundaries.
wstringPRUnichar*Raw character pointer to UTF-16 string, no string classes used.
AStringnsAStringUTF-16 string.
ACStringnsACString8-bit string. All bits are preserved across XPConnect boundaries.
AUTF8StringnsACStringUTF-8 string. Converted to UTF-16 as necessary when value is used across XPConnect boundaries.
DOMStringnsAStringUTF-16 string type used in the DOM. The same as AString with a few odd XPConnect exceptions: when the special JavaScript value null is passed to a DOMString parameter of an XPCOM method, it becomes a void DOMString. The special JavaScript value undefined becomes the string "undefined".

Create XPCOM Project in VC++

  • Use the right XULRunner SDK for your XULRunner release, I use xulrunner-1.9.2
  • Use a Microsoft compiler, I use Visual C++ 2005
  • Use the correct project settings

If you don't know how to create a VC++ project for XPCOM, look at my A Simple XPCOM Tutorial.

Now Start the Example

Let's specify a simple interface:

C++
#include "nsISupports.idl"

[scriptable, uuid(658ABC9E-29CC-43E9-8A97-9D3C0B67AE8B)]
interface ISample : nsISupports
{
    long        Add(in long a, in long b);  
    string        ReverseString(in string s);
    AString        ReverseAString(in AString nStr);
    wstring        ReverseUnichar(in wstring nStr);
    ACString    ReverseACString(in ACString nStr);
};

Remember to generate your own GUID.

The next step is to compile the IDL into a type-library (*.XPT) and a C++ header file (*.H), which we can use to define our implementation object. We have to use XPIDL.EXE twice, like this:

  • {path_to_ xulrunner-sdk }\bin\xpidl.exe -m header -I..\ xulrunner-sdk \idl {your_idl_file}
  • {path_to_ xulrunner-sdk }\bin\xpidl.exe -m typelib -I..\ xulrunner-sdk \idl {your_idl_file}

The generated H file actually has a skeleton implementation (commented out).

You can take the code and create implementation H and CPP files. They could look like this:

Header file:
C++
#ifndef _SAMPLE_H_
#define _SAMPLE_H_

#include "ISample.h>

#define SAMPLE_COMPONENT_CONTRACTID "@cn.ibm.com/XPCOM/sample;1"
#define SAMPLE_COMPONENT_CLASSNAME "Sample XPCOM Interface Layer"
#define SAMPLE_COMPONENT_CID  {0x658abc9e, 0x29cc, 0x43e9, 
        { 0x8a, 0x97, 0x9d, 0x3c, 0x0b, 0x67, 0xae, 0x8b } }
                      
//658abc9e-29cc-43e9-8a97-9d3c0b67ae8b
class CSample : public ISample
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_ISAMPLE

  CSample();
  virtual ~CSample();

  //additional member functions
};
#endif
CPP file:
C++
#include "Sample.h"
#include "nsStringAPI.h"
#include <stdio.h>

//This macro automatically adds the nsISupports entry
NS_IMPL_ISUPPORTS1(CSample, ISample)

CSample::CSample()
{
    /* member initializers and constructor code */
}

CSample::~CSample()
{
    /* destructor code */
}

/* long Add (in long a, in long b); */
NS_IMETHODIMP CSample::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
    *_retval = a + b;
    return NS_OK;
}

/*  string ReverseString(in string s); */
NS_IMETHODIMP CSample::ReverseString(const char *s, char **_retval)
{
    char sTemp[256] = {'\0'};
    int nLen = strlen(s);

    *_retval = (char*)NS_Alloc(sizeof(char)*256);
    for (int i = 0; i < nLen; i++)
    {
        sTemp[i] = s[nLen-1-i];
    }

    strcpy(*_retval, sTemp);
    
    return NS_OK;
}

/* AString ReverseAString (in AString nStr); */
NS_IMETHODIMP CSample::ReverseAString(const nsAString & nStr, nsAString & _retval)
{
    nsAutoString strAuto;
    strAuto.Assign(nStr);
    nsAutoString strAutoTemp;

    int nLen = strAuto.Length();
    
    wchar_t wchTemp[256] = {'\0'};
    
    for (int i = 0; i < nLen; i++)
    {
        wchTemp[i] = strAuto[nLen-1-i];
    }
    
    _retval.Assign(wchTemp);
    return NS_OK;    
}

/* wstring ReverseUnichar (in wstring nStr); */
NS_IMETHODIMP CSample::ReverseUnichar(const PRUnichar *nStr, 
                       PRUnichar **_retval NS_OUTPARAM)
{
    nsAutoString strAuto;
    strAuto.Assign(nStr);

    int nLen = strAuto.Length();
    *_retval = (PRUnichar*)NS_Alloc(nLen*sizeof(PRUnichar));

    PRUnichar nStrTemp[256] = {'\0'};

    for (int i = 0; i < nLen; i++)
    {
        nStrTemp[i] = strAuto[nLen-1-i];
    }

    wcscpy(*_retval, nStrTemp);    
        
    return NS_OK;
}

/* ACString ReverseACString (in ACString nStr); */
NS_IMETHODIMP CSample::ReverseACString(const nsACString & nStr, 
              nsACString & _retval NS_OUTPARAM)
{
    nsCAutoString strAuto;
    strAuto.Assign(nStr);
    nsCAutoString strAutoTemp;

    int nLen = strAuto.Length();
    
    char chTemp[256] = {'\0'};
    
    for (int i = 0; i < nLen; i++)
    {
        chTemp[i] = strAuto[nLen-1-i];    
    }

    _retval.Assign(chTemp);

    return NS_OK;
}

Lastly, we need to create the module implementation.

C++
#include "nsIGenericFactory.h"
#include "Sample.h"
  
NS_GENERIC_FACTORY_CONSTRUCTOR(CSample)

static nsModuleComponentInfo components[] =
{
  {
      SAMPLE_COMPONENT_CLASSNAME,
      SAMPLE_COMPONENT_CID,
      SAMPLE_COMPONENT_CONTRACTID,
      CSampleConstructor,
  }
};

NS_IMPL_NSGETMODULE("sample_module", components)

The XPCOM Client Project

The CPP file:
C++
#include "stdafx.h"
#include "nsCOMPtr.h"
#include "nsXPCOMStrings.h"
#include "nsXPCOM.h"
#include "../sample_xpcom/ISample.h"
#include "../sample_xpcom/Sample.h"
#include "nsServiceManagerUtils.h"
#include "nsStringAPI.h"

int _tmain(int argc, _TCHAR* argv[])
{
    nsresult rv;

    nsCOMPtr<nsiservicemanager> servMan;

    // Get Service manager
    rv = NS_GetServiceManager(getter_AddRefs(servMan));
    if (NS_FAILED(rv))
    {
        printf("ERROR: XPCOM error [%x].\n", rv);
        return -1;
    }

    // Get the Component object;
    nsCOMPtr<isample> iSample;
    rv = servMan->GetServiceByContractID(SAMPLE_COMPONENT_CONTRACTID, 
                   NS_GET_IID(ISample), getter_AddRefs(iSample));    
        
    // Or you can get like this
    //rv = servMan->GetService(NS_GET_IID(CSample), 
    //           NS_GET_IID(ISample), getter_AddRefs(iSample));

    if ( NS_FAILED(rv) )
    {
        NS_ShutdownXPCOM(nsnull);
        return -1;
    }

    int nFirstVal, nSecondVal, nResult;
    nFirstVal= 15; 
    nSecondVal = 10;
    iSample->Add(nFirstVal, nSecondVal, &nResult);

    _tprintf(_T("\nThe Result is : %d\n"), nResult);

    //---------------------------------------------------------------
    //----------------------- Using String types --------------------
    //---------------------------------------------------------------


    // IDL type        C++ Type        Purpose
    // string        char*            Raw character pointer to ASCII (7-bit) 
    //              string, no string classes used.
    //              High bit is not guaranteed across XPConnect boundaries.

    char chMainStr[256] = {'\0'};
    char *chRevStr;
    strcpy(chMainStr, "Test string");
    iSample->ReverseString(chMainStr, (char**)&chRevStr);
    printf("\nThe Main String: %s  The Reverted String : %s.\n", 
           chMainStr, chRevStr);

    //    IDL type    C++ Type    Purpose
    //    wstring        PRUnichar*    
    //               Raw character pointer to UTF-16 string, no string classes used.

    wchar_t wchMainStr[256] = {'\0'};
    wchar_t *wchRevStr;    

    wcscpy(wchMainStr, L"Test wstring");
    iSample->ReverseUnichar(wchMainStr, &wchRevStr);
    wprintf(L"\nThe Main String: %s  The Reverted String : %s.\n", 
            wchMainStr, wchRevStr);

    // IDL type            C++ Type        Purpose
    // AString            nsAString        UTF-16 string.
    nsString nsStringRev;
    nsString nsStringMain(L"Test AString");
    wcscpy(wchMainStr, nsStringMain.get());
        
    iSample->ReverseAString(nsStringMain, nsStringRev);
    wcscpy(wchRevStr, nsStringRev.get());
    wprintf(L"\nThe Main String: %s  The Reverted String : %s.\n", 
            wchMainStr, wchRevStr);

    // IDL type            C++ Type        Purpose
    // ACString            nsACString        8-bit string.
    //                                  All bits are preserved across XPConnect boundaries.
    nsCString nsCStringRev;
    nsCString nsCStringMain("Test ACString");
    strcpy(chMainStr, nsCStringMain.get());
    iSample->ReverseACString(nsCStringMain, nsCStringRev);
    strcpy(chRevStr, nsCStringRev.get());
    
    printf("\nThe Main String: %s  The Reverted String : %s.\n", 
           chMainStr, chRevStr);

    // Shutdown Service manager 
    NS_ShutdownXPCOM(nsnull);

    return 0;
}

References

For further details of XPCOM strings, the best reference is the API reference of:

Note: An easy and simple way of learning this is to just use the projects and debug with break point. If there are any suggestions, requests, or problems, please inform me.

History

  • 16/08/2010: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)