Previous Contents Index DocHome Next |
iPlanet Application Server 6.0 Programmer's Guide (C++) |
Chapter 3 Application Development Techniques
This chapter describes your application development tools (code editor, compiler, debugger, and so on), as well as the iPlanet Application Builder and sample applications.The following topics are included in this chapter:
Your Development Environment
Your Development Environment
Your development environment includes the following components:
Your application development tools (code editor, compiler, debugger, and so on).
If you are using iPlanet Application Builder, the application files are automatically placed in appropriate directories. If you are not using the iPlanet Application Builder, you should create a separate directory (<Code>$APP_ROOTDIR) to contain the files that belong to each application or project. The following table shows the suggested locations for these directories:The iPlanet Application Builder and sample applications that you installed using the iPlanet Application Server installation procedure, described on the product CD.
Accessing Libraries
To use classes from a particular file in one of the libraries, include that file at the start of your AppLogic code. You include a file by using the #include statement. Any code module can contain one or more #include statements to gain access to code in other files.When writing AppLogic objects, you will be working with the files in the iPlanet Application Server Foundation Class Library. These files are located in $IAS/include, where $IAS is the directory in which iPlanet Application Builder is installed. Typically, you will need at least the following files:
Example
When you write an AppLogic object, you make it a subclass of the GXAppLogic class. This class is in the file gxapplogic.h. Before you can reference the GXAppLogic class to derive the subclass, you must include the GXAppLogic class's file. The following example code shows how to include the file gxapplogic.h:class MyAppLogic : public GXAppLogic
Using Interfaces
You can use the interfaces in the iPlanet Application Server Foundation Class Library in either of the following ways:
Create a reference to the interface, in order to interact with an object that supports that interface. For example, to interact with a query object, you need to reference the IGXQuery interface.
Create a class that implements an interface, in order to write your own custom behavior for a particular type of object. For example, to implement a session ID object that creates session IDs in a way that is unique to your application, you implement the IGXSessionIDGen interface.
How to Reference Objects Through Interfaces
Once an interface is implemented in a class, and the class is instantiated into an object, calling code access the object through a pointer to the object's interface. This is the only way calling code can access an object: through its clearly-defined contract, the interface. When calling code accesses an object, it uses the interface pointer to access the methods in the interface. This use of interface pointers hides the internal implementation from calling code.For example, the following code shows how to access an object through an interface pointer:
// Get pointer to an IGXQuery interface by calling
// CreateQuery(), which returns such a pointer
HRESULT hr = CreateQuery(&pqry);
// Use the pointer to call SetTables(), which is
// a method of the IGXQuery interface
pqry->SetTables("customers, orders");
How to Implement Interfaces
When you implement an interface, you write a class that contains code to perform the behavior that is defined in the interface. The class declaration statement is similar to subclassing except that in place of the superclass name, you put the interface name. For example:class MySession : public IGXSession2 {
// code to implement the interface
The class inherits from the interface it wishes to implement, declares whatever variables are necessary for maintaining the object state, and overrides all the member functions of the interface.
For example, the IGXSession2 interface in the iPlanet Application Server Foundation Class Library contains the SetSessionData( ) and SaveSession( ) methods. The interface defines the parameters and return types for these methods. You can create a class to implement IGXSession2 and write code to make these methods work in any way you choose. You can even create several different classes that implement the interface in very different ways. Calling code accesses the objects instantiated from these classes only through their interfaces, and know nothing else about the objects. Therefore, different implementations can be used interchangeably.
When implementing an interface, you must provide an implementation for every method in the interface. However, the implementation can be simply a return statement. When working with interfaces provided by iPlanet, you can implement some methods so that they simply call the iPlanet version of the same method. To do this, make an instance of the original interface and use it to call the original method versions.
For example, suppose you implement the IGXSession2 interface in order to perform some special processing during a single method. For the other methods in the interface, you would simply call the original method as shown in the sample code below.
The following code appears in the header file:
class AcmeSession : public IGXSession
// Pointer to IGXSession instance to delegate to
AcmeSession(IGXSession2 *orig)
STDMETHOD(GetSessionData) (IGXValList **ppSessionData);
long GetShoppingCartItemCount();
The following code appears in the source file:
// You are making no changes to this method
// so call the original version
AcmeSession::GetSessionData(IGXValList **ppSessionData)
return original->GetSessionData(ppSessionData);
// You are customizing this method
long AcmeSession::GetShoppingCartItemCount()
if (GetSessionData(&data) == NOERROR &&
data->GetValInt("cart_item_count", &count);
Getting Information About Interfaces
You can find out whether an object is capable of providing particular services by calling the QueryInterface( ) method, which is provided by all interfaces in the libraries. QueryInterface( ) tells whether a given object implements a given interface (and, therefore, provides the services you need). QueryInterface( ) takes as a parameter the unique identifier of the interface in which you are interested. If the object implements that interface, it returns a pointer to the interface. Your code then uses this pointer to interact with the object.If QueryInterface( ) successfully obtains the interface pointer, it automatically calls AddRef( ) to increment the reference count on the object. Therefore, for every successful QueryInterface( ) call, your calling code must make a corresponding single Release( ) call, through the returned pointer, in order to match the implicit AddRef( ) call made by QueryInterface( ). Otherwise, a memory leak can occur.
Example
The following code shows how to query an interface:// Make sure the result set supports the IGXTemplateData
// interface (it always should)
if(((hr=pHRset->QueryInterface(IID_IGXTemplateData,
(LPVOID *)&pTD))==GXE_SUCCESS)&&pTD) {// Everything is fine, so continue; release when done
Instantiating Objects
Instantiation is the process of allocating an object to memory at runtime. An object is an instance of a class. The class defines the characteristics of a type of object. When an application runs, one or more objects can be instantiated, or created, from each class in the application. In an iPlanet Application Server application, most access to objects is accomplished through interfaces.
Declare a variable to refer to the object. For example:
Instantiate the object by calling the appropriate method. For example, in the following code, the GetSession( ) method is used to retrieve an instance of a session object.
- In this example, the variable pSess is declared using the IGXSession2 interface to specify that the variable will reference an object implemented from that interface. In step 2, you create the instance.
hr=GetSession(0, OB_APPNAME, NULL, &pSess);
- Calling the special instantiation and object retrieval methods and functions in the iPlanet Application Server Foundation Class Library takes the place of the new keyword. These methods and functions, such as GetSession( ), perform extra tasks above and beyond what is accomplished with the new keyword. The new functionality is performed in the iPlanet Application Server method code and takes place automatically.
Declaring and Defining Methods
Methods often return HRESULT, a 32-bit result code that equals zero (NOERROR) for success or non-zero for error conditions. In your AppLogic, if you create a virtual method that returns HRESULT, you must use the following macros for cross-platform portability:
STDMETHOD macro for the method declaration in the header file (.h).
For example, in the OBLogin.h file in the Online Bank sample application, the following STDMETHOD command declares the OBLogin AppLogic's Execute( ) method:STDMETHODIMP macro for the method definition in the source code file (.cpp).
class OBLogin : public OBBaseAppLogic
In addition, in the source file OBLogin.cpp, the following STDMETHODIMP command defines the Execute( ) method:
Reference Counting
It is necessary to free objects when they are no longer in use, just as you must free memory. The code that is using an object is responsible for freeing the object when it is no longer needed. The mechanism that calling code uses to accomplish this is called reference counting.You perform reference counting by using AddRef( ) and Release( ), which are provided by all interfaces in the libraries. The AddRef( ) and Release( ) methods are defined in the IGXObject interface, from which all the other interfaces inherit.
You can use these two methods to increment and decrement the count of references to any iPlanet Application Server object that your code uses. When you call AddRef( ), you are informing an object that you are using it. When you call Release( ), you are informing the object that you are finished using it. The object keeps track of how many other code modules are using it, and when the count drops to zero, the object deletes itself from memory.
Reference counting provides lifecycle control over objects to ensure proper housekeeping and avoid memory leaks. Each object contains an internal reference counter that tracks the number of other objects relying on it at runtime. When the reference count is decremented to zero (0), the object is deleted.
Unless otherwise noted, methods in the iPlanet Application Server Foundation Class Library that return objects automatically increment the reference count on these objects on behalf of your calling code. However, you must explicitly decrement the reference count in your AppLogic, using the object's Release( ) method, when a pointer to the instance is no longer needed.
For example, the following code shows how to release a query object. The CreateQuery( ) method returns a query object and performs one implicit AddRef( ). The caller is responsible for the matching Release( ) call for that implicit AddRef( ) call.
if(((hr=CreateQuery(&pQ))==GXE_SUCCESS)&&pQ) {
pQ->SetTables("OBAccount, OBTransaction,
OBTransactionType");In the following example, housekeeping for reference counters is performed in the destructor method.
GXDllLockDec(); // Update count of references to the
In more advanced applications, you may need to explicitly increment the reference count in your AppLogic using the object's AddRef( ) method. You might want to do this in order to ensure that the object is valid when using the object for a long time.
Even if you are extremely careful about reference counting, it is likely that some memory will not be released properly. Over time, such memory leaks will consume the available resources on your machine. Therefore, when using C++, it is advisable to use a tool capable of memory use analysis, such as Purify by Pure Atria.
Working with Data
When developing applications, you need to work with data of various types. This section describes the techniques provided to allow you to access and modify data. Manipulation of data will arise routinely throughout an application, such as when preparing data to pass into a function call, dealing with the return value from a function call, or dealing with the data returned by a database query.iPlanet Application Server applications can include the usual data types such as integers and strings. The libraries also provide some special types of data, such as globally unique identifiers (GUIDs), data objects such as IGXValList objects, and mechanisms such as memory buffer management and spin locks.
Managing Memory Buffers
The iPlanet Application Server Foundation Class Library provides the IGXBuffer interface to manipulate memory blocks. The IGXBuffer interface represents a block of memory that multiple objects can share, allowing the multiple users of an IGXBuffer object to control the lifetime of the memory block by using reference counting.Several methods return IGXBuffer objects, such as GetFields( ) in the IGXQuery interface. These methods automatically create the IGXBuffer object and return it. You can then use the methods in the IGXBuffer interface to access the data in the buffer after it is returned from the method call.
Other methods take IGXBuffer objects as parameters. Before calling such a method, you must create the buffer and place the appropriate data in it.
Call the function.
You must first specify the size of the memory block by calling Alloc( ) before calling any of the other methods in the IGXBuffer interface.
Examples
The first example calls a method that returns a buffer, and then uses the buffered data.HRESULT hr = pQuery->GetFields(&buff);
// ... work with buff, such as using GetAddress()
The following code allocates a buffer object and uses it to temporarily store a string. It then passes the buffer as a parameter to the Put( ) method in the GXTemplateMapBasic class.
pOutput=GXGetValListString(m_pValIn, "OUTPUTMESSAGE");
pBuffOutput=GXCreateBufferFromString(pOutput);
pTM->Put("OUTPUTMESSAGE", pBuffOutput);
Using Spin Locks
Use spin locks to ensure synchronous access to shared resources such as a counter variable used by several threads. Use spin locks for only short processes consisting of just one or several brief operations. Extensive or careless use of spin locks (such as for longer processes like memory allocation or ODBC calls) can reduce AppLogic performance. For longer processes, use critical sections instead. For more information, see Using Critical Sections.
Using a Spin Lock for General Operations
To use a spin lock, call the following functions in the following order:
Call the GXSYNC_INIT( ) function to initialize a synchronization variable (of type GXSYNCVAR) to be used to synchronize access to shared resources, via a spin lock, in subsequent operations.
Call the GXSYNC_LOCK( ) function to acquire exclusive access to the shared resource(s) that the specified spin lock protects. While your code owns the spin lock, other code cannot acquire it.
Perform the brief process or operations.
Call the GXSYNC_UNLOCK( ) function to release a spin lock that was acquired in a preceding GXSYNC_LOCK( ) call. Releasing the spin lock allows other code to acquire it.
Call the GXSYNC_DESTROY( ) function to remove a spin lock that is no longer needed. Calling GXSYNC_DESTROY( ) releases the system resources allocated for the spin lock. Subsequent calls to the spin lock are invalid. To use the spin lock again, you must subsequently initialize the spin lock using GXSYNC_INIT( ).
Example
The following code shows how to use a spin lock to perform the simple operation of incrementing a counter. In the class MyClass, the following member variables are declared:The following code appears in the constructor method:
The following code appears in the destructor method:
The following code appears in a method in the class:
Incrementing and Decrementing Variables
Alternatively, if you want to increment or decrement a variable using a spin lock, call the following functions in the following order:
Call the GXSYNC_INIT( ) function to initialize a synchronization variable (of type GXSYNCVAR) to be used to synchronize access to shared resources, via a spin lock, in subsequent operations.
GXSYNC_INC( ) and GXSYNC_DEC( ) call GXSYNC_LOCK( ) automatically before changing the variable, and call GXSYNC_UNLOCK( ) automatically after changing the variable.Call one of the following functions:
Call the GXSYNC_DESTROY( ) function to destroy a spin lock that is no longer needed. Calling GXSYNC_DESTROY( ) releases the system resources allocated for the spin lock. Subsequent calls to the spin lock are invalid. To use the spin lock again, you must subsequently initialize the spin lock using GXSYNC_INIT( ).
Using Critical Sections
In multithreaded programming, use critical sections in your code to ensure synchronization when multiple threads can manipulate the same object.
Call the GXInitCriticalSection( ) function to initialize a critical section object (of type GXCRIT_SECTION) to be used in subsequent operations to synchronize thread access to a particular process.
Call the GXEnterCriticalSection( ) function to obtain exclusive thread access to a shared resource before performing any operations on the protected resource. GXEnterCriticalSection( ) blocks until the thread is granted ownership.
Run the protected operations on the thread.
Call the GXLeaveCriticalSection( ) function to release exclusive thread access to shared resources after completing operations on the protected resource. Releasing ownership allows other threads to acquire the critical section.
Call the GXDeleteCriticalSection( ) function to destroy a critical section object that is no longer needed, which releases the system resources allocated for the critical section object. Subsequent calls to the critical section are invalid. To use the critical section again, you must subsequently initialize the critical section using GXInitCriticalSection( ).
Example
The following code implements a class that uses a critical section.GXDeleteCriticalSection(&myCS);
GXEnterCriticalSection(&myCS);
// Perform long, protected operation here.
GXLeaveCriticalSection(&myCS);
Working with Strings
When calling methods that return strings through out parameters, the caller is usually responsible for allocating the memory buffer for the string and for passing in the size of the buffer. The methods and functions called will fill the buffer with the string value. For example:hr = m_pValIn->GetValString("PHONE", buff, sizeof(buff));
printf("Phone number is %s\n", buff);
Numerous methods and functions in the libraries provide ways to specify and retrieve string values. For more information, see the relevant class, interface, or function description in the <Italic>iPlanet Application Server Foundation Class Reference.
Working with IGXValList Objects
An IGXValList object is an unordered list of named values. IGXValList objects are supported by the IGXValList interface. You use the GXCreateValList( ) function to create an IGXValList object, and you use the following commands to manipulate IGXValList objects:
the SetVal( ) methods in the IGXValList interface to specify values in an IGXValList object
The following example shows how to create an IGXValList object, populate it with database connection properties, and then pass it as a parameter to CreateDataConn( ):the GetVal( ) methods in the IGXValList interface to retrieve values from an IGXValList object
GXSetValListString(m_pProps, "DSN", "ksample");
GXSetValListString(m_pProps, "DB", "ksample");
GXSetValListString(m_pProps, "USER", "kdemo");
GXSetValListString(m_pProps, "PSWD", "kdemo");
// Create a database connection using properties
hr = CreateDataConn(0, GX_DA_DRIVER_ODBC, m_pProps,
IGXValList *pList=GXCreateValList();
// Specify database connection properties
GXSetValListString(pList, "DSN", OB_DSN);
GXSetValListString(pList, "DB", "");
GXSetValListString(pList, "USER", OB_USER);
GXSetValListString(pList, "PSWD", OB_PASSWORD);
// Create a database connection using properties
hr=CreateDataConn(0, GX_DA_DRIVER_DEFAULT, pList,
m_pContext, ppConn);
Working with GUIDs
Each registered AppLogic has a Globally Unique Identifier (GUID) associated with it. A GUID is a 128-bit hexadecimal number and has an associated GUID struct.You can use the following commands to manipulate GUID structs:
GXGetValListGUID( ) and GXSetValListGUID( ) functions
When passing a GUID to a macro, such as GXDLM_DECLARE or GXDLM_IMPLEMENT, you pass in a GUID struct. This format is shown in the following code example, taken from the source file OBLogin.cpp:// Set the GUID for OBLogin to
// {C1B5E720-6153-11D1-A1AE-006008293C54}
{ 0xC1B5E720, 0x6153, 0x11D1, { 0xA1, 0xAE, 0x00, 0x60, 0x08, 0x29, 0x3C, 0x54 } };
The following code is from a header file and shows how the GUID is passed to the GXDLM_DECLARE macro. OBLogin is the name of the AppLogic class associated with the GUID struct stored in OBLoginGUID.
GXDLM_DECLARE( OBLogin, OBLoginGUID);
Note that the string version of the GUID is embedded in the comments (for readability purposes only) and that the GUID struct is what gets parsed. However, in some cases, the string version of the GUID is passed, such as with the NewRequest( ) method.
For more information about GUIDs, see Requests, AppLogic Names, and GUIDs of , "Writing Server-Side Application Code."
Working with Binary Large Objects (BLOBs)
A binary large object (BLOB) is a large block of bits that can be stored in a database. A BLOB is useful for storing any large piece of data, such as pictures or sounds, that do not need to be interpreted by the database. Use the following methods for manipulating BLOB data in your AppLogic:
SetValueBinary( ) or SetValueBinaryPiece( ) in the IGXTable interface for inserting or updating BLOB values in a table
GetValueBinary( ) or GetValueBinaryPiece( ) in the IGXResultSet interface for retrieving BLOB values returned in a result set
GetValBLOB( ) or SetValBLOB( ) in the IGXValList interface for retrieving or assigning BLOB values in an IGXValList object
Example
The following code retrieves a BLOB value from a database.pQuery->SetTables("blobtable");
hr = pConn->ExecuteQuery(0, pQuery, NULL, NULL, &pRS);
hr = pRS->GetRowNumber(&nRows);
pBlobChunk = new LPBYTE[65536];
hr = pRS->GetValueBinaryPiece(1, expectSize, &pBlobChunk,
pRS->GetValueSize(1, &gotSize);
fprintf(stderr, "got a full chunk, size = %d\n", gotSize);
fprintf(stderr, "got a partial chunk, size = %d\n", gotSize);
Working with Dates and Times
Date and time values are implemented as a GXDATETIME struct. You can use the GXGetCurrentDateTime( ) function to obtain the system time.The following example shows how to retrieve and print the current system time:
GXGetCurrentDateTime(&curtime);
printf("Time is [%02d/%02d/%02d %02d:%02d:%02d:%01d]\n",
Exporting Classes
In order for your classes to be loaded properly at runtime, you must export some specific functions. iPlanet Application Server expects to find these exported functions when it loads your shared library at runtime. The exported functions are required to fully initialize the shared library and to create instances from the classes in it.
In a header file (.h), call the GXDLM_DECLARE macro to associate a class in a dynamically loadable, shared library module (DLM) with an already declared GUID struct.
The following example shows how these macros are used. The following code fragment appears in the header file:In a source file (.cpp) that is associated with the header file, call the GXDLM_IMPLEMENT_BEGIN macro to begin a block of one or more GXDLM_IMPLEMENT calls.
Call the GXDLM_IMPLEMENT macro to establish to the iPlanet Application Server the entry point in a dynamically loadable, shared library module (DLM) for one exported class.
Call the GXDLM_IMPLEMENT_END macro to end a block of one or more of GXDLM_IMPLEMENT calls.
class OBLogin : public OBBaseAppLogic
GXDLM_DECLARE( OBLogin, OBLoginGUID);
The following code fragment appears in the source file:
// {C1B5E720-6153-11D1-A1AE-006008293C54}
{ 0xC1B5E720, 0x6153, 0x11D1, { 0xA1, 0xAE, 0x00, 0x60,
GXDLM_IMPLEMENT( OBLogin, OBLoginGUID);
Using Events
In an iPlanet Application Server environment, you can create and use named events. The term event is widely used to refer to user actions, such as mouse clicks, that trigger code. However, the events described in this section are not caused by users. Rather, an event is a named action that you register with the iPlanet Application Server. The event occurs either when a timer expires or when the event is activated from application code at runtime.Events are stored persistently in the iPlanet Application Server, and are removed only when your application explicitly deletes them. Typical uses for events include periodic backups, reconciling accounts at the end of the business day, or sending alert messages. For example, you can set up an event that sends an email to alert your company's buyer when inventory levels drop below a certain level.
Each event has a name, a timer (optional), and one or more actions to take when the event is triggered. Application events have the following characteristics:
Each event can cause the execution of one or more actions, which can include sending email or running an Applogic.
You can set up events to occur at specific times or at intervals, such as every hour or once a week. You can also trigger an event by calling the event by name from code. When an event's timer goes off or it is called from code, the associated action occurs.Actions can be synchronous or asynchronous with the calling environment.
Multiple actions can be configured to execute concurrently with one another, or serially, one after the other.
Multiple actions are executed in a specific order (the order in which they are registered).
Request data can be passed to an application event in an IValList object.
The Application Events API
iAS uses two interfaces to support events:
The IGXAppEventMgr interface manages application events. This interface defines methods for creating, registering, triggering, enabling, disabling, enumerating, and deleting events.
For more details, see the entries for these interfaces in the iPlanet Application Server Foundation Class Reference.The IGXAppEventObj interface represents the defined events an application supports. This interface defines methods not only for getting or setting attributes of an event, but also for adding, deleting, or enumerating actions of the event.
Creating a New Application Event
To access an IGXAppEventMgr object, use the C++ helper function GXContextGetAppEventMgr( ):HRESULT GXContextGetAppEventMgr(
IGXContext *pContext
IGXAppEventMgr **ppAppEventMgr);The pContext parameter is a pointer to an IContext object, which provides access to iPlanet Application Server services. Specify a value of m_pContext.
The ppAppEventMgr is a pointer to the returned manager object.
After creating the IGXAppEventMgr object, you can create an application event (an instance of IGXAppEventObj) by calling CreateEvent() on the IGXAppEventMgr object.
You must then register the event, or make iAS aware of it, by calling RegisterEvent(). Further, you must also instruct iAS to enable the event for access by calling EnableEvent(). Once the event is registered and enabled, you can trigger it by hand using TriggerEvent().
Using an Application Event
You can perform any of the following tasks with an event by using the associated methods in the IAppEventMgr object:
Within the event object itself, you can set and examine the event's attributes as well as define actions for the event. Use the methods in the IAppEventObj interface:
For more details about these methods, see the iPlanet Application Server Foundation Class Reference.
Using Cookies
Cookies are a mechanism that Web applications can use to store information on the client (Web browser) side of the application. Cookies are variables that your application sends to the browser to be stored there for a specified length of time. Each time a Web browser requests an HTML page in your application, the cookies from that browser are sent to the application.Cookies are domain-specific and can take advantage of the same Web server security features as other data interchange between your application and the server. Thus, cookies are useful for privately exchanging data between your application and the Web browser.
Some browsers do not support cookies, but they are supported by all versions of Netscape Navigator and by Microsoft Internet Explorer version 2.0 and later.
Sending a Cookie
To send a cookie, call SetVariable( ). For example:SetVariable("preference", "green");
Referencing a Cookie
Whenever an AppLogic is executed, all cookies stored in the Web browser are sent to the AppLogic as part of the AppLogic's input parameters. The iPlanet Application Server receives the cookies as part of the AppLogic request, along with other input parameters for the AppLogic. When iPlanet Application Server runs the AppLogic, it passes all the input parameters to the AppLogic, including the cookies. The input parameters are passed in the input IGXValList object.Therefore, to reference a cookie from AppLogic code, use the AppLogic's m_pValIn variable, which refers to the input IGXValList. The name of the parameter is the same as the name you specified when you called SetVariable( ) to send the cookie to the Web browser. For more information about m_pValIn, see Passing Parameters to AppLogic Objects of , "Writing Server-Side Application Code."
Previous Contents Index DocHome Next
Copyright © 2000 Sun Microsystems, Inc. Some preexisting portions Copyright © 2000 Netscape Communications Corp. All rights reserved.
Last Updated April 26, 2000