Appendix D Creating C Components


Write C components

After you generate method skeletons, prototypes, and implementation templates, write the code for each method in the method implementation file. You can include C or C++ functions in C components. Jaguar provides C routines for common C component tasks (see Chapter 5, "C Routines Reference" in the Jaguar CTS API Reference).

To adapt a C++ class for use as a C component, you must write C wrappers. For details, see "C components that are wrappers for C++ classes".

Jaguar Manager creates template files for each method when you define the method signatures (or method prototypes) and generate skeleton routines. You can modify the template files to implement the method bodies, or you can code your methods from scratch according to the rules laid out here.

Note   Function overloading is not supported for C components.

You can also include Jaguar routines to:

Define implementation functions

Each method in the Jaguar component definition is implemented by a C function with the same name as the method.

Implementation function return codes

Method implementation functions must return CS_RETCODE. Your implementation function can return the following values:

In general, you should return CS_SUCCEED unless a fatal error occurs. Returning CS_FAIL prevents the client stub from receiving output parameter values. Use an inout or output parameter if you need to communicate method status information to the client application.

Calling conventions

The Jaguar server calls methods using a specific C calling convention. Follow these rules to ensure compatibility with the Jaguar method calling convention:

Parameter datatypes

"Datatypes for C method implementation functions" shows the datatypes displayed in Jaguar Manager, the datatypes used by C components, and the argument modes. The left column contains the datatype name as it displays in Jaguar Manager. The second and third columns contain the names of the corresponding C datatypes for input, inout, and output parameters.

If the Jaguar Manager method definition returns a value other than ResultSet or ResultSets, an additional output parameter is added to the front of the implementation function's parameter list. This additional parameter receives the "logical return code" for the method invocation, as described in "Logical method return values".

Datatypes for C method implementation functions

Jaguar Manager

Mode

C Datatype

boolean

input
inout, output, return

CS_BIT
CS_BIT *

byte
(a single byte)

input
inout, output. return

CS_BINARY
CS_BINARY *

char
(a single character)

input
inout, output. return

CS_CHAR
CS_CHAR *

float

input
inout, output. return

CS_REAL
CS_REAL *

double

input
inout, output. return

CS_FLOAT
CS_FLOAT *

integer<16>

input
inout, output. return

CS_SMALLINT
CS_SMALLINT *

integer<32>

input
inout, output. return

CS_INT
CS_INT *

integer<64>

input
inout, output. return

CS_LONG
CS_LONG *

binary

input
inout, output. return

CS_BINARY_HOLDER *
CS_BINARY_HOLDER *

string

input
inout, output. return

CS_LONGCHAR_HOLDER *
CS_LONGCHAR_HOLDER *

string<255>

input
inout, output. return

CS_CHAR *
CS_CHAR *

timestamp

input
inout, output. return

CS_DATETIME
CS_DATETIME *

Argument modes

Argument modes specify how an argument is passed. Arguments can have one of these modes:

All parameters specified as input are passed by value except for those parameters declared as string, string<255>, or binary in Jaguar Manager. Except for binary and string parameters, Jaguar always preallocates sufficient space for inout, output, or return parameters. binary and string parameters are mapped to special datatypes, and you may need to reallocate space for the output value as described below.

string<255> parameters

string<255> parameters are passed as a CS_CHAR *. On input, the Jaguar server null-terminates CS_CHAR parameter values using the length meta-information associated with the datatype. On output, updated CS_CHAR parameter values must be null-terminated.

Note   string<255> parameter values cannot be longer than 255 bytes. Use string parameters if your application requires larger values.

string and binary parameters

string parameters are passed in a CS_STRING_HOLDER structure. binary parameters are passed in a CS_BINARY_HOLDER structure. These structures are defined in jagpublic.h as follows:

typedef struct _cs_longchar_holder
{
CS_LONGCHAR *value;
CS_INT length;
} CS_STRING_HOLDER;

typedef struct _cs_longbinary_holder
{
CS_LONGBINARY *value;
CS_INT length;
} CS_BINARY_HOLDER;

#define CS_LONGCHAR_HOLDER CS_STRING_HOLDER
#define CS_LONGBINARY_HOLDER CS_BINARY_HOLDER

To allow backward compatibility with code that was written for Jaguar version 1.1, you can use CS_LONGCHAR_HOLDER in place of CS_STRING_HOLDER, and CS_LONGBINARY_HOLDER in place of CS_BINARY_HOLDER.

On input, the value field contains the input value and the length field specifies the input length. For output, you can set a new value and length in the structure as follows:

The following example calls the JagFree and JagAlloc routines to reallocate a larger value buffer:

JagFree(myholder->value);
myholder->value = JagAlloc(new_length);
if (myholder->value == NULL)
{
JagLog(JAG_TRUE, "Out of memory!\n");
return CS_FAIL;
}
memcpy(myholder->value, new_value, new_length);
myholder->length = new_length;

NULLs

NULLs cannot be passed to or returned by method calls. Instead of using NULL for string parameters, pass zero-length values.

Logical method return values

If the Jaguar Manager method definition returns a value other than ResultSet or ResultSets, the C function signature contains an additional parameter in the first position. This parameter functions as a logical return value for method invocations. When the C function returns, the output value of this parameter is forwarded to the client, and the client receives it as the return value for the stub method invocation. Datatype mappings for this added parameter are the same as for an output parameter.

If the Jaguar Manager method definition returns ResultSet or ResultSets, you must use the C Result Set API calls to build the result set or sets to be sent to the client, as described in "Methods that return row results".

Implementing the method behavior

In most cases, the implementation of C component methods require no special coding. Simply add code to the method body that contains the application logic to respond to the input parameter values and assign the correct return values to inout, output, and return parameters.

The exceptions to this rule are:

Components that require instance specific data

C components do not contain private data. To allocate separate data for instances of the same component, use the JagSetInstanceData and JagGetInstanceData routines.

The Jaguar server provides two functions for managing instance-specific data:

Chapter 5, "C Routines Reference" in the Jaguar CTS API Reference contains reference pages for these routines.

C components that are wrappers for C++ classes

Since methods in a C component must be implemented as C functions, you must code C wrappers for C++ classes.

Note   Beginning in version 2.0, Jaguar provides direct support for running C++ classes as components, as described in Chapter 16, "Creating CORBA C++ Components" Sybase supports the technique described here, but recommends that you create a C++ component to run your C++ classes directly.

The procedure for creating a wrapper for a C++ class is as follows:

  1. Code a C create function that instantiates the C++ object and stores the object reference as instance-specific data. For example, if the C++ object is StockTrade, create could be implemented as follows:
    CS_RETCODE CS_PUBLIC create() {
      StockTrade *st_ref;
      /*
    ** Create an instance of the C++
    ** StockTrade object.
    */
    st_ref = new StockTrade();
      /* 
    ** Associate it with the Jaguar component
    ** instance.
    */
    if (JagSetInstanceData((CS_VOID *)st_ref)
    != CS_SUCCEED)
    {
    return CS_FAIL;
    }
      return CS_SUCCEED;
    }
  2. For each C++ method, code a C wrapper function that retrieves the C++ object reference and uses it to call the C++ method. For example, the following shows a C wrapper to call a StockTrade::buyStock C++ method:
    CS_RETCODE CS_PUBLIC buyStock (
    CS_CHAR *ticker,
    CS_INT n_desired,
    CS_INT n_bought)
    {
    StockTrade *st_ref;
    if (JagGetInstanceData((CS_VOID *)&st_ref)
    != CS_SUCCEED)
    {
    return CS_FAIL;
    }
    st_ref::buyStock(ticker, n_desired,
    n_bought);
    return;
    }
  3. Code a destroy function that retrieves the C++ object reference and destroys it. For example:
    CS_RETCODE CS_PUBLIC destroy() {
    StockTrade *st_ref;
    if (JagGetInstanceData((CS_VOID *)&st_ref)
    != CS_SUCCEED)
    {
    return CS_FAIL;
    }
      delete st_ref;
    return CS_SUCCEED;
    }
  4. Define the Jaguar C component to include the C wrapper functions as its methods.

Methods that interact with remote database servers

A Jaguar server uses a connection cache to maintain a pool of connections from the Jaguar server to database servers. A component can connect to a database server using an existing connection in a connection cache without creating a new connection.

Note   Jaguar's transactional model works only with connections obtained from the Jaguar Connection Manager. Connections that you open yourself will not be affected by Jaguar transactions.

For more information about coding connection management routines into components, see Chapter 28, "Using Connection Management".

Methods that return row results

To return row results, call the result-set routines listed in Chapter 5, "C Routines Reference" in the Jaguar CTS API Reference. "Sending result sets from a C or C++ component" explains the call sequence and contains examples.

Share data between C or C++ components

Components in the same package can share data--that is, variable values. For example, a counter that tracks how many objects have been created for a single component could be used as a shared variable. Shared variables are organized into collections. These variables are referred to as shared because components in the same package can read and update the same data. A collection can contain any number of shared variables. Shared variables can be identified by name or by index number. Shared variables are initialized as null and are not saved when the server is shut down.

Because it is important to maintain the integrity of the shared data in shared variables, a single read or update operation on a shared variable is atomic. Atomic means that an operation on data will complete before any other operations can access that data. Multiple reads and updates on any number of shared variables in a single collection can be synchronized by locking that collection.

Note   You cannot use shared variables in components that are configured for automatic failover, because these components cannot use local shared resources. See "Transactions tab component properties" for more information. If you need to share data, you can store shared data in a remote database. See "Thread-safety features" for more information.

To share data between components, you must include the jagpublic.h file in the C source file.

Procedure for sharing data

The general procedure for sharing data is:

  1. Create or retrieve references to collections - The component must first create a collection before creating a shared variable in that collection. Creating a collection automatically retrieves a reference to the new collection. If the collection already exists, the component must retrieve a reference to the collection before creating a shared variable.
  2. Lock collections - Before creating a shared variable or reading and updating shared variables, lock the collection. Locking a collection ensures that the integrity of a shared variable will be maintained when the shared variable is read and updated by the same method multiple times. The component does not have to lock a collection if you are executing only one read or update on a shared variable.
  3. Create or retrieve references to shared variables - The component must first create a shared variable before reading or updating the shared variable. Creating a shared variable automatically retrieves a reference to the new shared variable. If the shared variable already exists, the component must retrieve a reference to the shared variable before reading or updating a shared variable.
  4. Read and update shared variables - After creating or retrieving a reference to a shared variable, the component can read and update the shared variable.
  5. Unlock collections - After reading and updating all shared variables in a collection, unlock the collection. Unlocking a collection immediately after the component instance has completed all operations on a shared variable allows other component instances to access the shared variable right away.
  6. Release shared variable and collection references - After reading and updating shared variables, release the reference to the shared variable. After all operations on shared variables in a collection have been completed, release the reference to the collection. Component objects use memory efficiently by releasing references immediately after the component object has completed operations on a shared variable or collection.

You can also list all collection names on the server by calling the JagGetCollectionList routine. For more information see "List all collections".

Create shared variables and collections

The component must create the collection before it can create shared variables.

  1. Create a collection and return a reference to the collection using the JagNewCollection routine. The component object must have a reference to a collection before calling any other routines on the collection.
  2. Create a shared variable in a collection and return a reference to the shared variable using the JagNewSharedData or JagNewSharedDataByIndex routine. The component instance must have a reference to a shared variable to access the contents of that shared variable.

Create collections

To create a new collection, call the JagNewCollection routine. This routine:

Lock level must be set to one of the following:

JAG_LOCKCOLLECTION - allows locks to be set on collections

JAG_LOCKDATA - does not allow locks to be set on collections

Create shared variables in collections

To create a new shared variable, call the JagNewSharedData or JagNewSharedDataByIndex routine. These routines create a shared variable value initialized to NULL. JagNewSharedData creates a new shared variable or returns a reference to an existing shared variable by name. JagNewSharedDataByIndex creates a new shared variable or returns a reference to an existing shared variable by index number. A shared variable created by index can only be retrieved or updated by index. Similarly, a shared variable created by name can only be retrieved or updated by name. Since a reference is returned, you do not need to follow these routines with the JagGetSharedData or JagGetSharedDataByIndex routine.

For both routines, *pExists is set to:

Lock and unlock collections

Locking a collection is strictly advisory. Use JagLockCollection and JagLockNoWaitCollection routines to lock collections. Even though a collection is locked, the JagGetSharedValue and JagSetSharedValue routines can still read and update the shared variables in the collection. To ensure that multiple read or update operations on any shared variable in a collection are atomic, lock the collection before executing read or update operations on the shared variables in the collection.

Call the JagGetLockLevel routine to determine a collection's isolation mode. If the collection's isolation mode is JAG_LOCKCOLLECTION, then the component object can lock the collection. Otherwise, the lock will be rejected.

If you call the JagLockCollection routine to lock a collection that is locked by another component, JagLockCollection waits until the collection is unlocked by the other component, then locks the collection. If the lock is successful, JAG_SUCCEED is returned. If the collection has already been locked by the calling object, this routine does not lock the collection again and JAG_SUCCEED is returned.

The JagLockNoWaitCollection routine does not wait until a locked collection is unlocked; the JagLockNoWaitCollection routine immediately returns execution to the calling routine. This routine returns JAG_SUCCEED and sets *pLocked to JAG_TRUE if the collection was not locked or if the collection is already locked by the same calling object. If the collection was locked by another component object, JAG_SUCCEED is still returned but *pLocked is set to JAG_FALSE.

The JagLockCollection and JagLockNoWaitCollection routines return JAG_FAIL if an error, such as the collection's isolation mode is JAG_LOCKDATA, occurs.

Call the JagUnlockCollection routine to release a lock on a collection. A locked collection is automatically released when the component object's method execution is completed. However, to make your application more efficient and prevent deadlocks, unlock a collection when the component object is finished updating or reading the shared variable in the collection so that other component objects can access the collection right away.

Read and update shared variables

Before reading or updating the shared variable, the component object must retrieve references to the collection and shared variable.

  1. Call the JagGetCollection routine to retrieve a reference for a collection. If the component object has just created the collection, the component object doesn't need to call this routine.
  2. Call the JagGetSharedData or JagGetSharedDataByIndex routine to retrieve a reference to a shared variable. If the component object has just created the shared variable, the component object doesn't need to call either of these routines.
  3. Call the JagGetSharedValue routine to retrieve a shared variable value.
  4. Call the JagSetSharedValue routine to assign a new value to the shared variable.

Retrieve references for collections

The JagGetCollection routine returns a reference to the specified collection. Once the component instance has retrieved a reference, the component object can lock and unlock the collection, create a new shared variable in the collection, or retrieve a reference to an existing shared variable.

If the collection exists, JAG_SUCCEED is returned and **ppCollection is set to the collection reference. If the collection does not exist, JAG_FAIL is returned and **ppCollection is set to NULL.

Retrieve references to shared variables

The JagGetSharedData and JagGetSharedDataByIndex routines return a reference to the specified shared variable. The component object must have already retrieved the collection reference before calling these routines. When calling the JagGetSharedData routine, you specify the shared variable by name. When calling the JagGetSharedDataByIndex routine, you specify the shared variable by index number.

For both routines, if the shared variable exists, JAG_SUCCEED is returned and **ppData is set to the shared variable reference. If the shared variable does not exist, JAG_FAIL is returned and **ppData is set to NULL for both routines.

Retrieve shared variable values

The JagGetSharedValue routine retrieves the value for a specified shared variable and places the value in a buffer. The component object must have retrieved the shared variable reference before executing this routine. The component object must create a buffer in which to copy a value. The buffer must be large enough to hold any value that can be stored in the shared variable. You must specify the buffer (and its size) in which the value is to be copied. The buffer must be large enough to contain the value. If the value is too large for the buffer, JAG_FAIL and the size of the value are returned.

If the value is successfully copied into the buffer, JAG_SUCCEED and the number of bytes copied to the buffer are returned. If *outlen is 0, then there was no value to copy.

Update shared variables with new values

The JagSetSharedValue routine copies a value to a specified shared variable. The component object must have retrieved the shared variable reference before calling this routine. The component object must pass a pointer to the value you want the component object to copy to the shared variable. This routine copies the value to the shared variable. You must specify the size of the value. If the value is a null-terminated string, you must include the length of the null terminator in the length of the string.

Jaguar maintains the values of shared data in its own memory space. When JagSetSharedValue() copies the data, it does not copy the pointer to the data. Similarly, JagGetSharedValue() copies the data into a buffer supplied by the caller, it does not place a pointer to the data in the user's buffer.

If the new value is copied to the shared value, JAG_SUCCEED is returned. If an error occurs, JAG_FAIL is returned.

Release shared variable and collection references

After a method finishes all operations on a collection, release the reference and all shared variable references. This helps to prevent memory leaks. Releasing collection and shared variable references does not release the shared variable values.

First, release shared variable references and then release the collection reference. To release shared variable references, call the JagFreeSharedDataHandle routine, passing the shared variable reference as input. To release collection references, call the JagFreeCollectionHandle routine on the collection reference.

If the shared variable or collection reference is released, JAG_SUCCEED is returned. If an error occurs, JAG_FAIL is returned.

List all collections

Call the JagGetCollectionList routine to retrieve a list of all the collection names on the server. The server returns a JagNameList structure. This routine can be called in conjunction with administering the Jaguar server. Call the JagFreeCollectionList routine to free the memory allocated for the JagNameList structure.

The JagGetCollectionList routine returns a reference to a JagNameList structure that includes all the collection names defined on the Jaguar server. The JagNameList structure is:

typedef struct _jagnamelist
{
SQLINT num_names;
SQLPOINTER *names;
} JagNameList;

where:

num_names is the number of array elements.

*names is an array of num_names elements; each element points to a null-terminated collection name.

Methods that set transactional state

Methods in a transactional component should call one of the transaction state primitive routines listed in Chapter 5, "C Routines Reference" of the Jaguar CTS API Reference.

Even if your component is not transactional, you should call one of these methods to explicitly specify whether the instance should be deactivated.

For transactional components, choose the routine that reflects the state of the work that the component is contributing to the transaction, as follows:

For nontransactional components, call either JagCompleteWork or JagRollbackWork to deactivate and destroy the component instance. To keep the instance active, call JagContinueWork or JagDisallowCommit.

If a method does not explicitly set transaction state before returning, the default behavior is JagContinueWork.

Customize the creation and destruction of components

To customize what happens when a component instance is created or destroyed, write customized code into the create (create.c.new) and destroy (destroy.c.new) routine templates that are generated by Jaguar Manager. create and destroy are typically used to manage instance-specific data that the component requires. For example, some methods might need to be executed in a certain sequence. You can customize the create and destroy routines to keep track of which methods have been executed. For details on managing instance-specific data, see "Components that require instance specific data".

The create and destroy routines are optional. You can also implement create and destroy in another source file and ignore the generated templates. The create and destroy routines cannot have parameters and cannot return result sets.

create routine

The Jaguar server calls create when creating a new instance of the component. The signature for create is:

CS_RETCODE CS_PUBLIC create()

create must return CS_SUCCEED.

destroy routine

The Jaguar server calls destroy when destroying an instance of the component. The signature for destroy is:

CS_RETCODE CS_PUBLIC destroy()

destroy must return CS_SUCCEED.

Handle errors in your C component

As a general rule, code C component methods to handle unrecoverable errors as follows:

  1. Write detailed error descriptions to the server log file using JagLog.
  2. Call JagSendMsg to send a descriptive message to the client.
  3. If the component is transactional, call JagDisallowCommit or JagRollbackWork as appropriate.
  4. Return CS_FAIL to indicate failed execution.

Chapter 5, "C Routines Reference" of the Jaguar CTS API Reference contains reference pages for these routines.

 


Copyright © 2000 Sybase, Inc. All rights reserved.