Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Advanced CORBA Programming wit C++ - M. Henning, S. Vinoski.pdf
Скачиваний:
65
Добавлен:
24.05.2014
Размер:
5 Mб
Скачать

IT-SC book: Advanced CORBA® Programming with C++

Forgetting to Deallocate a Variable-Length out Parameter

As you saw at the beginning of Section 7.14.13, you must deallocate variable-length out parameters unless you are using _var types. We strongly recommend that you habitually use _var types for out parameters and return values. In that way, you cannot forget to deallocate memory and therefore your code will not suffer memory leaks.

7.15 Mapping for Exceptions

Until now, we have mostly ignored the possibility of errors during request invocation. Even though the C++ mapping makes a remote invocation look like a local function call, the reality of networking means that a remote invocation is more likely to fail than a local function call. Remote invocation obviously will fail if a client cannot reach a server because of network failure. Other reasons for remote call failure include resource limitations (for example, the client may run out of file descriptors) and implementation limits (your ORB may, for example, impose a maximum size limit on parameters).

As mentioned in Section 4.10, the ORB indicates infrastructure-related failures by raising system exceptions. This means that every invocation can raise a system exception even if it does not have an IDL raises expression. In addition, if an operation has a raises expression, it can raise user exceptions.

The C++ mapping provides several exception base classes in the CORBA namespace. They are arranged in an inheritance hierarchy as follows:

namespace CORBA {

 

// ...

// Abstract

class Exception {

public:

 

// ...

 

};

 

class UserException : public Exception {

// Abstract

// ...

 

};

 

class SystemException : public Exception {

// Abstract

// ...

 

};

 

// Concrete system exception classes:

class UNKNOWN : public SystemException { /* ... * / }; class BAD_PARAM : public SystemException { /* ... */ }; // etc...

}

The abstract base class Exception acts as the root of the inheritance tree. UserException and SystemException are also abstract base classes; all concrete system exceptions (such as UNKNOWN and BAD_PARAM) are derived from

278

IT-SC book: Advanced CORBA® Programming with C++

SystemException, and all user exceptions are derived from UserException. The resulting inheritance hierarchy looks like the diagram shown in Figure 7.8. This exception hierarchy allows you to catch all exceptions in a single catch clause, or you can catch specific exceptions selectively. The following code shows an example of handling exceptions for a Thermostat::set_nominal operation:

Figure 7.8 Exception class hierarchy.

CCS::Thermostat_var ts = ...;

CCS::TempType new_temp = ...;

try { ts->set_nominal(new_temp);

}catch (const CCS::BadTemp &) {

//New temp out of range

}catch (const CORBA::UserException &) {

//Some other user exception

cerr << "User exception" << endl;

}catch (const CORBA::OBJECT_NOT_EXIST &) {

//Thermostat has been destroyed

}catch (const CORBA::SystemException &) {

//Some other system exception

cerr << "System exception" << endl;

}catch (...) {

//Non-CORBA exception -- should never happen

This code uses the exception hierarchy to specifically distinguish an out-of-range error condition from other user-defined errors and also specifically tests for non-existence of the target object. Other user exceptions and system exceptions are dealt with generically.

Note the final catch handler. This handler runs only if the operation invocation raises a non-CORBA exception. This should never happen because the CORBA specification does not allow an ORB to raise exceptions other than CORBA exceptions. If an operation

279

IT-SC book: Advanced CORBA® Programming with C++

raises a user exception that is not in the raises expression for the operation or if an operation throws a (non-CORBA) C++ exception, the ORB must translate it into the UNKNOWN system exception. However, not all ORB implementations are diligent in this respect. (If the ORB fails to intercept and translate such an exception to UNKNOWN, the unexpected function will be called in standard C++ environments.)

To be safe, it is best to handle system exceptions and "impossible" C++ exceptions together in a generic catch handler that simply rethrows the exception:

CCS::Thermostat_var ts = ...;

CCS::TempType new_temp = ...;

try { ts->set_nominal(new_temp);

}catch (const CCS::BadTemp &) {

//New temp out of range

}catch (const CORBA::UserException &) {

//Some other user exception

cerr << "User exception" << endl;

}catch (const CORBA::OBJECT_NOT_EXIST &) {

//Thermostat has been destroyed

}catch (...) {

//Other system exceptions or non-CORBA exceptions

//are an SEP (somebody else's problem).

throw;

}

Typically, you will not handle exceptions in this much detail for every call. It is easier to handle one or two specific exceptions that are of interest and to install default exception handlers higher up in the call hierarchy. A common technique is to enclose all of main in a try block with a generic catch handler. This technique allows you to at least detect an uncaught exception and to terminate with an error message instead of simply having your program abort.

A generic catch handler also has the advantage that it can deal with new system exceptions. The list of system exceptions is open-ended and occasionally is extended to accommodate new features of CORBA. If your code has a generic catch handler for system exceptions, you can at least report a generic CORBA error instead of a completely unknown error.

Note that the preceding code catches exceptions by reference to const. This is preferable to catching exceptions by value.

Catching exceptions by reference is more efficient than catching them by value because it allows the compiler to avoid creating a temporary.

If you catch a base exception by value and then rethrow the exception, you will slice off the derived part of the exception if its actual (dynamic) type is derived from the base type.

280

IT-SC book: Advanced CORBA® Programming with C++

You cannot catch Exception, SystemException, and UserException by value because they are abstract base classes.

Also note that servers never throw exceptions by pointer, so catching them by reference or by value is the only available option.

7.15.1 Mapping for System Exceptions

System exceptions are mapped as follows:

// In namespace CORBA...

class Exception { public:

Exception(const Exception &); virtual ~Exception();

Exception & operator=(const Exception &);

virtual void

_raise() = 0;

protected:

Exception();

};

enum CompletionStatus {

COMPLETED_YES,

COMPLETED_NO,

COMPLETED_MAYBE };

class SystemException : public Exception { public:

 

SystemException();

 

 

SystemException(const SystemException &);

 

SystemException(

minor,

 

ULong

 

CompletionStatus

status

 

);

 

SystemException &

~SystemException();

 

operator=(const SystemException &);

ULong

minor() const;

 

void

minor(ULong);

 

CompletionStatus

completed() const;

 

void

completed(CompletionStatus);

static SystemException * _downcast(Exception *);

static const SystemException *

_downcast(const Exception *);

virtual void

_raise() = 0;

};

281

IT-SC book: Advanced CORBA® Programming with C++

Note that SystemException is still an abstract base class because it has a pure virtual _raise function. Concrete system exceptions, such as UNKNOWN, simply inherit from SystemException and provide an implementation for the inherited _raise function. We discuss the purpose of the _raise function in more detail in Section 7.15.7.

The copy constructor and assignment operator make deep copies and are provided because C++ exceptions must be copyable.

SystemException();

SystemException(ULong minor, CompletionStatus status);

The default constructor creates a system exception with a completion status of COMPLETED_NO and a minor code of zero, whereas the second constructor permits you to set the completion status and minor code at instantiation time. The constructors are of little interest in the client because clients have no reason to create exceptions; on the server side, the constructors are needed to create exceptions that can be thrown.

ULong minor(); void minor(ULong);

These member functions are an accessor and a modifier for the minor member of a system exception. As we point out in Section 4.10, CORBA does not specify the semantics of the minor member, so it is probably best never to use these functions unless you can tolerate ORB-specific code.

CompletionStatus completed() const; void completed(CompletionStatus);

These member functions are an accessor and a modifier for the completed member of a system exception. As we discuss in Section 4.10, the completed member indicates whether an exception was raised before or after the application code in the server was invoked or indicates COMPLETED_MAYBE if the client-side run time could not make that determination. Knowledge of whether or not an operation completed can be important when the client decides whether it should retry an operation.

static SystemException * _downcast(Exception *);

static const SystemException * _downcast(const Exception *);

This operation is provided for non-standard C++ compilers that lack support for exceptions or run-time type identification (RTTI). For consistency, _downcast is also generated for standard C++ environments, even though it is not required there.

_downcast allows you to test the dynamic type of an exception at run time:

try { tmstat_ref->set_nominal(500);

282

IT-SC book: Advanced CORBA® Programming with C++

}catch (const CORBA::Exception & e) {

//Check what sort of exception it is...

const CORBA::SystemException * se;

if ((se = CORBA::OBJECT_NOT_EXIST::_downcast(&e)) != 0) {

//It is an OBJECT_NOT_EXIST exception

}else if ((se = CCS::BadTemp::_downcast(&e)) != 0) {

//It is a BadTemp exception

}else {

//It is some other exception

}

//Do not deallocate se here -- the exception

//still owns the memory pointed to by se.

}

_downcast returns a non-null pointer if the actual type of the exception matches the expected type of the exception and returns null otherwise. Note that the pointer returned from _downcast points at memory still owned by the exception, so there is no need to deallocate memory.

There is little point in calling _downcast in an environment that supports exceptions. It is much easier and clearer if you simply install a catch handler for each exception type. Moreover, in a standard C++ environment you can also use a dynamic cast instead of

_downcast:

try { tmstat_ref->set_nominal(500);

}catch (const CORBA::Exception & e) {

//Check what sort of exception it is...

const CORBA::SystemException * se;

if (se = dynamic_cast<const CORBA::OBJECT_NOT_EXIST *>(&e)) {

//It is an OBJECT_NOT_EXIST exception

}else if (se = dynamic_cast<const CCS::BadTemp * >(&e)) {

//It is a BadTemp exception

}else {

//It is some other exception

}

}

This code does exactly the same thing as the previous example but uses RTTI instead of the generated _downcast function.

In CORBA 2.2 and earlier ORBs, _downcast is called _narrow. There is no difference between the two functions other than the name. The _narrow function was renamed to _downcast with the CORBA 2.3 specification to avoid confusion with the _narrow member function for object references.

7.15.2 Semantics of System Exceptions

283

IT-SC book: Advanced CORBA® Programming with C++

For some system exceptions, the CORBA specification precisely states the error conditions that cause these exceptions to be raised. For other system exceptions, the specification only makes suggestions as to the circumstances in which an ORB should raise a particular exception. The vagueness of the specification is deliberate in these cases because the error conditions that must be reported can vary widely with the environment of an ORB.

Here is a list of the CORBA system exceptions and their meanings.

BAD_CONTEXT

An operation can raise this exception if you invoke an operation that has an IDL context clause but the passed context object does not contain the values expected by the operation.

BAD_INV_ORDER

The caller has invoked operations in the wrong order. For example, an ORB can raise this exception if an ORB-related call is made before the ORB run time was initialized.

BAD_OPERATION

This indicates that an object reference denotes an existing object but that the object does not support the operation that was invoked. You will rarely see this exception because the C++ mapping makes it impossible to invoke an operation on an object that does not support it. However, you can get this exception if you are using the DII incorrectly or if the client and server have been compiled from conflicting IDL definitions (definitions that use the same interface names but different operations for those interfaces).

BAD_PARAM

A parameter passed to a call is out of range or otherwise considered illegal. Some ORBs raise this exception if you pass a null pointer to an operation.

BAD_TYPECODE

An attempt was made to transmit a malformed type code—for example, a type code with an invalid TCKind value (see Chapter 16).

COMM_FAILURE

This exception is raised if communication is lost while an operation is in progress. At the protocol level, the client sends a request to the server and then waits for a reply containing the results. If the connection drops after the client has sent the request but before the reply has arrived, the client-side run time raises COMM_FAILURE.

Some ORBs incorrectly raise COMM_FAILURE instead of TRANSIENT if they cannot establish a connection to the server. If this is the case for your ORB, you should put pressure on the vendor to fix it.

DATA_CONVERSION

This exception indicates that the on-the-wire representation of a value could not be converted into its native representation or vice versa. This exception typically is raised for mismatches in character codesets or for failure to correctly convert between floatingpoint and fixed-point representations of values.

FREE_MEM

The ORB run time could not deallocate memory—for example, because of heap corruption or because a memory segment was locked.

IMP_LIMIT

284

IT-SC book: Advanced CORBA® Programming with C++

This exception indicates that an implementation limit was exceeded in the ORB run time. There are a variety of reasons for this exception. For example, you may have reached the maximum number of references you can hold simultaneously in your address space, the size of a parameter may have exceeded the allowed maximum, or your ORB may impose a maximum on the number of clients or servers that can run simultaneously. Your ORB's documentation should provide more detail about such limits.

INITIALIZE

Initialization of the ORB run time failed—for example, because of a configuration error or because a network interface is down.

INTERNAL

This exception is a general catch-all for internal errors and assertion failures and typically indicates a bug in the ORB.

INTF_REPOS

The ORB has detected a failure relating to the Interface Repository (such as an Interface Repository that is unreachable).

INVALID_TRANSACTION

An operation invocation carried an invalid transaction context. This exception is raised only for invocations on objects that are transactional [21].

INV_FLAG

This exception is raised by invocations via the Dynamic Invocation Interface if an invalid invocation flag is passed to the ORB by the application.

INV_IDENT

An IDL identifier is syntactically invalid. This exception is raised, for example, if an attempt is made to add an invalid identifier to the Interface Repository or if an illegal operation name is passed to a DII request.

INV_OBJREF

This exception indicates that an object reference is internally malformed. For example, the repository ID may have incorrect syntax or the addressing information may be invalid. This exception is usually raised by string_to_object if the passed string does not decode correctly. Some ORBs incorrectly raise this exception when they should be raising OBJECT_NOT_EXIST. If this is the case for your ORB, you should put pressure on the vendor to fix it.

Some ORBs raise INV_OBJREF if you attempt to invoke an operation via a nil reference. Although raising INV_OBJREF in this case is compliant, you cannot rely on it because calling through a nil reference has undefined behavior (it will cause a core dump in many implementations).

INV_POLICY

A number of CORBA interfaces provide operations that allow applications to select desired qualities of service based on policy objects. This exception indicates that an inappropriate policy object was passed to an operation, or, when a set of policy objects is passed, that incompatible policy objects are contained in the set.

MARSHAL

A request or reply from the network is structurally invalid. This error typically indicates a bug in either the client-side or the server-side run time. For example, if a reply from the server indicates that the message contains 1,000 bytes but the actual message is shorter or longer than 1,000 bytes, the ORB raises this exception. MARSHAL can also be caused if

285

IT-SC book: Advanced CORBA® Programming with C++

you use the DII or DSI incorrectly and pass parameter types that disagree with an operation's IDL definition.

NO_IMPLEMENT

This exception indicates that even though the operation that was invoked exists (it has an IDL definition), no implementation for that operation exists. NO_IMPLEMENT can, for example, be raised by an ORB if a client asks for an object's type definition from the Interface Repository (IFR), but no Interface Repository is provided by the ORB (the IFR is an optional CORBA component).

NO_MEMORY

The ORB run time ran out of memory at some stage during a call. You can check the completion status to see whether it happened before or after the operation was invoked in the server.

NO_PERMISSION

This exception can be raised by ORBs that provide a Security Service [21] if the caller has insufficient privileges to invoke an operation.

NO_RESOURCES

The ORB has encountered a general resource limitation. For example, the run time may have reached the maximum permissible number of open connections.

NO_RESPONSE

The DII can be used to make deferred synchronous invocations that need not block the caller while the invocation is in progress. NO_RESPONSE is raised if you attempt to retrieve the results of an invocation before the results are available.

OBJECT_NOT_EXIST

This exception is an authoritative indication that the reference for the request is stale (denotes a non-existent object). If you receive this exception, you can safely conclude that the reference to the object is permanently non-functional and therefore you should clean up any application resources (such as database entries) you may have for that object.

OBJ_ADAPTER

This exception is raised only on the server side. Typically, it indicates an administrative mismatch. For example, you may be trying to register a server under a name that is already used by another server.

PERSIST_STORE

This exception indicates a persistent storage failure, such as a corrupted or unreachable database.

TRANSACTION_REQUIRED

This exception applies only to transactional objects and is raised if an operation can be invoked only as part of a transaction but the caller did not establish a transaction before invoking the operation.

TRANSACTION_ROLLEDBACK

A request was not carried out because its associated transaction was rolled back. This exception gives clients that use transactions a chance to realize that further work inside the current transaction will be fruitless because the transaction has already rolled back (and will therefore never commit successfully).

TRANSIENT

TRANSIENT indicates that the ORB attempted to reach the server and failed. It is not an indication that the server or the object does not exist. Instead, it simply means that no

286

IT-SC book: Advanced CORBA® Programming with C++

further determination of an object's status was possible because it could not be reached. TRANSIENT is typically raised if connectivity to the server cannot be established— things may work if you try again later.

UNKNOWN

This exception is raised if an operation implementation raises a non-CORBA exception or if an operation raises a user exception that does not appear in the operation's raises expression. UNKNOWN is also raised if the server returns a system exception that is unknown to the client. This can happen if the server uses a later version of CORBA than the client and if new system exceptions have been added to the later version.

7.15.3 Mapping for User Exceptions

The IDL compiler maps each user exception to a class that derives from UserException. The generated class is mapped like a structure with an additional constructor. Here is an example:

exception DidntWork { long requested; long min_supported; long max_supported; string error_msg;

};

This generates the following code:

class DidntWork : public CORBA::UserException { public:

CORBA::Long requested; CORBA::Long min_supported; CORBA::Long max_supported; CORBA::String_mgr error_msg;

DidntWork();

 

DidntWork(

requested,

CORBA::Long

CORBA::Long

min_supported,

CORBA::Long

max_supported,

const char *

error_msg

);

DidntWork(const DidntWork &); ~DidntWork();

DidntWork & operator=(const DidntWork &); static DidntWork * _downcast(CORBA::Exception *);

};

As you can see, the mapping is similar to the one for structures. For each exception member, a corresponding public data member is generated into the class. Like structures, exceptions manage memory for their members, so when an exception is destroyed, the class recursively deallocates memory allocated to its members.

287

IT-SC book: Advanced CORBA® Programming with C++

User exceptions get an additional constructor that accepts one parameter corresponding to each exception member. This constructor is useful mainly on the server side because it allows you to construct an exception completely within a throw statement. The remainder of the member functions take care of copying and assignment.

Here is example code that illustrates how you can catch this exception in the client and print the data in the exception:

try { some_ref->some_op();

} catch (const DidntWork & e)

{

cerr << "Didn't work:" <<

endl;

cerr << "\trequested

: " << e.requested << endl;

cerr << "\tmin_supported: " << e.min_supported << endl;

cerr << "\tmax_supported: " << e.max_supported << endl;

cerr << "\tmessage

: " << e.error_msg << endl;

}

 

 

As with system exceptions, the static _downcast member function provides safe downcasting. You will have little reason to use _downcast; it is easier to catch the exception directly or, for standard C++ compilers, to use a dynamic cast (see page 313).

7.15.4 Exception Specifications

The C++ mapping makes it optional for an IDL compiler to generate exception specifications for the proxy methods invoked by the client. Consider the following IDL definition:

exception Failed {}; interface Foo {

void can_fail() raises(Failed);

};

There are two valid signatures for the can_fail function in the proxy:

virtual void can_fail() = 0; // OR:

virtual void can_fail() throw(CORBA::SystemException, Failed) = 0;

In practice, it does not matter which version is generated by your IDL compiler. C++ does not associate any static checks with exception specifications, and the behavior visible to the client at run time is the same whether or not exception specifications are generated.

7.15.5 Exceptions and out Parameters

288

IT-SC book: Advanced CORBA® Programming with C++

If you call an operation and that operation raises an exception, you cannot use the return value from the operation (after all, the operation did not return a value because it failed). A more subtle error occurs if you forget that variable-length out parameters are cleared by the mapping on entry to the call. This means that if an operation raises an exception, you cannot assume that variable-length out parameters will still have the same values they had before the call:

CORBA::String_var name = CORBA::String_dup("Hello");

// ...

try {

 

vf->get_name(name);

 

} catch (const CORBA::SystemException &) {

// Disaster!!!

cout << name << endl;

}

 

This code uses the out parameter name if an exception is raised. However, because name is variable-length, it is set to null by the String_out constructor when you pass it to the get_name function. This means that the value of name is null in the exception handler, and attempts to dereference it are likely to cause a core dump.

In general, if an operation fails, you cannot assume that either the return value or inout and out parameters have defined values. Of course, in parameters are guaranteed to still have their original values if an operation raises an exception.

7.15.6 ostream Insertion

Many ORBs, as an extension to the C++ mapping, provide ostream inserters with the following signatures:

ostream & operator<<(ostream &, const CORBA::Exception &); ostream & operator<<(ostream &, const CORBA::Exception *);

The inserters permit you to insert an exception into a C++ ostream. For example:

try { some_ref->some_op();

} catch (const CORBA::Exception & e) {

cerr << "Got an exception: " << e < endl;

}

The C++ mapping does not require that an ORB provide ostream inserters for exceptions, so this feature is non-standard.[2] If provided, the inserters typically print the unqualified name of the exception, such as BAD_PARAM, or the repository ID of the exception, such as IDL:omg.org/CORBA/BAD_PARAM:1.0. Depending on your ORB, the inserters may also show the completion status and minor code for system exceptions.

289

IT-SC book: Advanced CORBA® Programming with C++

[2] A future version of the C++ mapping will likely make ostream inserters a standard feature.

If your ORB does not provide ostream inserters for exceptions, you can easily write your own:

//Generic ostream inserter for exceptions. Inserts the exception

//name, if available, and the repository ID otherwise.

static ostream &

operator<<(ostream & os, const CORBA::Exception & e)

{

CORBA::Any tmp; tmp <<= e;

CORBA::TypeCode_var tc = tmp.type(); const char * p = tc->name();

if (*p != '\0') os << p;

else

os << tc->id(); return os;

}

This code relies on types Any and TypeCode to achieve generic insertion of exceptions; we discuss these features in detail in Chapters 15 and 16.

You can also create overloaded ostream inserters for more derived exceptions to control the formatting of specific system and user exceptions (see Section 8.5.2 for an example).

7.15.7 Mapping for Compilers that Lack C++ Exception Support

CORBA defines an alternative exception mapping for compilers that lack C++ exception handling support. The alternative mapping adds an additional parameter to every operation signature. Client code explicitly must test the value of that parameter after every call to check whether an exception was raised. This works, but it is not nearly as elegant as using real C++ exceptions.

By now, almost all C++ compilers support C++ exception handling even if they are not yet fully standard C++ compliant, so the alternative mapping is rapidly becoming obsolete. For this reason, we do not cover it here. If you need to use the alternative mapping, consult the specification [17a] for details.

The Exception::_raise function we saw in Section 7.15.1 is provided for environments that mix old non-exception-aware code and exception handling code in the same binary. (This can happen if you have legacy code that does not use C++ exceptions and you now want to link the legacy code with exception-aware code written later.) _raise is implemented in the generated code as follows:

void SomeException::_raise()

290