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:
Wide | Narrow |
---|
nsAString | nsACString |
nsString | nsCString |
nsAutoString | nsCAutoString |
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.
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.
Context | Class | Notes |
---|
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 /wstring s | 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 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. |
wstring | PRUnichar* | Raw character pointer to UTF-16 string, no string classes used. |
AString | nsAString | UTF-16 string. |
ACString | nsACString | 8-bit string. All bits are preserved across XPConnect boundaries. |
AUTF8String | nsACString | UTF-8 string. Converted to UTF-16 as necessary when value is used across XPConnect boundaries. |
DOMString | nsAString | UTF-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:
#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:
#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:
#include "Sample.h"
#include "nsStringAPI.h"
#include <stdio.h>
NS_IMPL_ISUPPORTS1(CSample, ISample)
CSample::CSample()
{
}
CSample::~CSample()
{
}
NS_IMETHODIMP CSample::Add(PRInt32 a, PRInt32 b, PRInt32 *_retval)
{
*_retval = a + b;
return NS_OK;
}
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;
}
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;
}
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;
}
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.
#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:
#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;
rv = NS_GetServiceManager(getter_AddRefs(servMan));
if (NS_FAILED(rv))
{
printf("ERROR: XPCOM error [%x].\n", rv);
return -1;
}
nsCOMPtr<isample> iSample;
rv = servMan->GetServiceByContractID(SAMPLE_COMPONENT_CONTRACTID,
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);
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);
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);
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);
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);
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.