Level: Introductory Rick Parrish (rfmobile@swbell.net), Independent consultant, Freelance
01 May 2001 Component authoring is covered in this fourth installment to our five-part introductory series on XPCOM. XPCOM stands for Cross-Platform Component Object Model. The purpose of XPCOM is to provide a mechanism for writing modular software objects (components) over a reasonably large number of platforms. In the preceding articles in this series (see Resources) I discussed how to initialize XPCOM and how to use its existing components. This installment and the next will discuss how to create a component. Definitions
Getting a component defined sufficiently to actually start writing some code involves about five steps. For each XPCOM component you will need to:
- Generate a class-id UUID
- Generate an interface UUID for each interface
- Designate a contract ID to be associated with the class-id above
- Define your interfaces in IDL
- Compile the IDL and use the generated stubs to implement your component
Let's take a look at these five steps in more detail. First, generate a class-id. You can use GUIDGEN or some other GUID/UUID generating tool. GUIDGEN is convenient enough because it places the results on the clipboard so you can paste the values directly into your IDL file. While you've got your UUID generator fired up, take care of step two by generating one more UUID for each interface you think your component will implement. In step three, make up a terse yet meaningful (or at least memorable) contract ID. Remember that the contract ID is the textual name for your component, not the numeric name. In step four, open up a text editor and paste in your interface IDs, class ID, and contract ID and then write your interface definitions. In the last step, feed your IDL file to the XPIDL compiler to validate its accuracy. The compiler will produce an XPT file with all of the type library information, along with an optional C++ stub file. If your IDL file passes the XPIDL compiler without complaint, double check your work by examining the generated C++ stubs to make sure that what you were trying to say through IDL comes out to what you meant in C++. Now that you've got a good set of C++ stubs, we're ready to start coding. As an example, I'm going to describe a fictional component that might be used to interface an XPCOM application to a Global Positioning Satellite receiver (or "GPS receiver" for short). A GPS receiver is an
accessory we can attach to our computer to provide us with a few pieces of information: the current time, our position on the surface of the earth, and sometimes our elevation. To give this fictional component a little more meaning, suppose you were given a nice cross-platform mapping tool that could scale and rotate two dimensional mapping data on the screen. The mapping data is presented in an XML-formatted vector list known as SVG, and the programming logic and screen layouts are implemented using XUL (hint: XUL is the dead giveaway that it's based on the Mozilla browser, so we know we can use XPCOM components with it). Your mission is to turn this tool into a moving map display by feeding real-time positioning data from a GPS receiver. To make things a bit more complicated, you've been tasked with supporting three different types of GPS receivers, and each has its own protocol. See Resources for a real-world example of three such protocols.
XUL is an XML-based method for specifying the layout of a user interface. Instead of writing code specific to some operating system's GUI that creates panels, buttons, text labels, check boxes, or list and
tree controls, you create one or more text files filled with XML tags directing the layout of the various GUI elements. If you plan to write a Mozilla-based application that takes advantage of the Mozilla GUI engine, you will be using XUL as a part of your design. The strategy for our component, then, is to come up with a common interface and then create separate implementations for each of the various types of GPS receivers out there. Each implementation will share this one common interface, allowing the mapping application's code to blissfully use this interface without needing to know any specifics about the actual device attached. It's a good idea to put the directory for a new component project in a
directory parallel to the nsSample component. (See also the
section on installing and building the Mozilla source as described in
Build
the lizard
, in Part 3 of this series). On my laptop, the directory for the nsSample component is /mozilla/xpcom/sample. So for this GPS example, I created a new directory whose path is /mozilla/xpcom/gps. I'm assuming all three implementations will live in the same module, so only one directory is needed. Listing 1 shows an interface that might be used to model a GPS receiver.
[scriptable, uuid(1BDC2EE0-E92A-11d4-BCC0-0060089296CB)]
interface mozIGPS : nsISupports
{
boolean Open(in string strDevice);
boolean Close();
string Reason(in boolean bClear);
readonly attribute double latitude;
readonly attribute double longitude;
readonly attribute double elevation;
readonly attribute double gpstime;
};
|
The interface ID for the IGPS interface is placed directly into the IDL file. XPIDL will allow you to insert text directly into the C++ generated code through the use of '%' tags so I could use that technique
to place my chosen class ID and contract ID into the same IDL source file for safekeeping. The XPIDL compiler translates the IDL text in Listing 1 into an XPT file and a C++ header file with a stub C++ class definition commented out by #if 0 ... #endif. At this point, you need to do a couple of things.
- Copy the stubs into a separate .cpp which will serve as the basis
for our implementation file.
- Create a C++ header file with an almost empty class declaration that
publicly inherits from the interface defined in the XPIDL generated C++
header file.
- Don't forget to
#include the generated file into our hand-edited
one. Only our implementation file should #include the hand-edited header
file.
Do not edit the generated header file. If the IDL source ever changes,
it will be erased. That's why we create our own separate header file that
we can safely edit. Remember, up to this point everything has been
generated by the XPIDL compiler. From now on out, it is the programmer's
task to fill in the stubbed member functions with real working code. Listing 2 shows the generated code. Taking a look at the generated file in Listing 2, notice the
NS_IMPL_ISUPPORTS macro which implements
QueryInterface, AddRef, and Release
for us. Remember, we haven't written any code yet -- this was done for us.
Also look at how the IDL compiler mapped the IDL definitions for the Open
and Close methods and also how it mapped the attributes Latitude and
Longitude to accessor methods GetLatitude and GetLongitude. In every case, the stubbed implementation does nothing but return
NS_ERROR_NOT_IMPLEMENTED. You can take the XPIDL generated text that lies between #if
0 and #endif and copy it to a separate file that would
become your implementation source file. If your component does not require
sharing any carnal knowledge of its internals with other related
components, then you can leave the class declaration in the implementation
source file. In other words, your implementation file includes the IDL
generated header file, then immediately defines a C++ class that
inherits from the one in the header file. If other C++ classes need to
know about the internals of your implementation's class, then you'll want
to drop that out into its own C++ header file. In any case, you will want
to add a few #define symbols to your implementation's source file to
define the contract ID, class ID, and an optional class name. Since I'm describing three different implementations of the same interface, each one will need its own class ID and contract ID. See Listing 3 for an example.
Listing 4 shows the header file for the three implementing classes. All it does is include the XPIDL generated header file and declare the three
implementing classes. Each implementing class defines a constructor and a
virtual destructor, and expands a couple of macros. The NS_DECL_ISUPPORTS and NS_DECL_MOZIGPS macros actually declare that each class will be implementing the
nsISupports and mozIGPS interfaces. At this point, since no actual
implementing code has been written, each of the three classes uses the
same stubbed-out implementation shown in Listing 5.
#include "mozGPS.h"
/* NMEA Implementation */
NS_IMPL_ISUPPORTS1(mozGPSNMEAImpl, mozIGPS)
mozGPSNMEAImpl::mozGPSNMEAImpl()
{
NS_INIT_ISUPPORTS();
/* member initializers and constructor code */
}
mozGPSNMEAImpl::~mozGPSNMEAImpl()
{
/* destructor code */
}
/* boolean Open (in string strDevice); */
NS_IMETHODIMP mozGPSNMEAImpl::Open(const char *strDevice, PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* boolean Close (); */
NS_IMETHODIMP mozGPSNMEAImpl::Close(PRBool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double latitude; */
NS_IMETHODIMP mozGPSNMEAImpl::GetLatitude(double *aLatitude)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double longitude; */
NS_IMETHODIMP mozGPSNMEAImpl::GetLongitude(double *aLongitude)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double elevation; */
NS_IMETHODIMP mozGPSNMEAImpl::GetElevation(double *aElevation)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* readonly attribute double gpstime; */
NS_IMETHODIMP mozGPSNMEAImpl::GetGpstime(double *aGpstime)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/* string Reason (in boolean bClear); */
NS_IMETHODIMP mozGPSNMEAImpl::Reason(PRBool bClear, char **_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
|
We now have a complete XPCOM module that implements three components. All that remains to make them into real, usable components is to replace the return NS_ERROR_NOT_IMPLEMENTED;
statements with actual code.
Conclusion
We have covered a lot of terrain so far -- and will soon reach our destination. We have defined a problem space for our sample component, drafted an interface and actually begun writing some code. In the next and
final installment to this series, we will complete the code to produce a "buildable" component and look at debugging the result.
Resources
About the author  | |  | Rick Parrish has held an interest in computers since high school and in electronics even longer. He originally pursued an education in electrical engineering but discovered that software, unlike hardware, did not require smelly vats of ferric chloride or run the risk of burnt fingers just to perform a design change. Rick has been programming in C/C++ for A LONG TIME(tm) but has also done heaps of work in VB, Delphi(Pascal), and a handful of assembly languages. He still manages to squeeze in a project or two that requires hot solder. His current opinions are that while Windows 2000 is neat-o, Ogg Vorbis is cool, and the Linux 2.4 kernel with IPTables definitely rocks! He is
currently interested in starting an open source project to develop tools for software modeling design. Rick can be reached at rfmobile@swbell.net.
|
Rate this page
|