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

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

9.8 Raising Exceptions

Servant methods raise IDL exceptions to indicate unexpected errors. For example, the set_nominal operation of the Thermostat interface from our example CCS module can raise the BadTemp exception:

#pragma prefix "acme.com"

module CCS {

typedef short TempType;

interface Thermometer {/*...*/};

interface Thermostat : Thermometer { struct BtData {

TempType

requested;

TempType

min_permitted;

TempType

max_permitted;

string

error_msg;

};

 

exception BadTemp { BtData details; };

TempType

get_nominal();

TempType

set_nominal(in TempType new_temp)

 

raises(BadTemp);

};

};

The corresponding method in the skeleton class has the following signature:

namespace POA_CCS {

class Thermostat : public virtual Thermometer { public:

// ...

virtual CCS::TempType set_nominal(CCS::TempType new_temp) throw(CORBA::SystemException, CCS::BadTemp) = 0;

// ...

};

}

The exception specification for set_nominal allows the user-defined CCS::BadTemp exception to be thrown, and, as with all servant methods, set_nominal may also throw CORBA system exceptions.

9.8.1 Exception Throwing Details

An implementation of set_nominal in a derived servant class might be written as follows:

CCS::TempType

336

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

Thermostat_impl:: set_nominal(

CCS::TempType new_temp

) throw(CORBA::SystemException, CCS::BadTemp)

{

const CCS::TempType MIN_TEMP = 50, MAX_TEMP = 90; if (new_temp < MIN_TEMP || new_temp > MAX_TEMP) {

BtData bt;

bt.requested = new_temp; bt.min_permitted = MIN_TEMP; bt.max_permitted = MAX_TEMP; bt.error_msg =

CORBA::string_dup("temperature out of range");

throw CCS::BadTemp(bt);

}

// ...

}

Our example first checks the requested temperature setting to ensure that it is within the permitted range. If the temperature is outside the permitted range, we create an instance of the BtData structure and initialize it with information about the error. We then use the structure instance to construct and throw an instance of CCS::BadTemp to inform the client of the error.

The C++ mapping allows you to throw exceptions by value and catch them by reference. Each exception class supplies a constructor that takes an initialization parameter for each exception member. This allows you to create and throw exception instances all in the same statement, as shown in the preceding example. The alternative of allocating exceptions on the heap and throwing them by pointer would merely add memory management responsibilities for clients, requiring them to remember to delete thrown exceptions.

Keep in mind that a servant method is allowed to throw only the exceptions explicitly listed in its exception specification. This includes all CORBA system exceptions because of the appearance of the CORBA::SystemException base class in all servant exception specifications. The C++ run time will prevent a servant method from throwing any exception not listed in its exception specification even if that exception is thrown by a function called directly or indirectly by the servant method.

Unfortunately, ORB implementations cannot count on C++ exception specifications to prevent servants from throwing illegal exceptions. Some C++ compilers that do not fully support standard C++ supply no error or warning messages if a servant method has a less restrictive exception specification than the skeleton method it is overriding. For example, with some C++ compilers we can rewrite our Thermostat_impl::set_nominal signature without any exception specification, and the C++ compiler will not complain (assuming the method is also declared in the same way in the class definition):

CCS::TempType

337

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

Thermostat_impl:: set_nominal(

CCS::TempType new_temp

) // oops, missing exception specification!

{

// same code as before

}

Moreover, some widely used C++ compilers do not properly implement or support exception specifications, meaning that IDL compilers are forced to elide them when generating code for certain platforms.

To ensure that only allowed exceptions are thrown from a servant method, the ORB and the skeleton enclose all servant method invocations in a catch block that traps all CORBA and non-CORBA exceptions. Any exceptions that should not be thrown by the servant method, including CORBA user-defined exceptions and general C++ exceptions, are caught by the ORB and turned into the CORBA::UNKNOWN system exception instead. This catch block acts as a barrier that prevents exception-related application errors from entering the ORB run time, where they are unexpected and could cause the entire application to abort. The catch block also prevents the client application from receiving user-defined exceptions that were not declared in the operation's raises clause.

9.8.2 Throwing CORBA System Exceptions

We have repeatedly pointed out that all servant methods are allowed to throw CORBA system exceptions. The main reason is to allow the ORB to raise exceptions for any error conditions it encounters when performing its location, activation, and request and response delivery. However, this opens the door for servant method implementations to also throw CORBA system exceptions directly.

Unfortunately, throwing CORBA system exceptions from within servant methods can lead to systems that are difficult to debug. For example, a servant method might want to throw the CORBA::BAD_PARAM exception if one of its input parameters has an unexpected value or to throw the CORBA::NO_MEMORY exception to indicate that it could not successfully allocate a variable-length out parameter. However, the ORB also uses these exceptions to indicate errors that it encounters in attempting to deliver requests or replies. When a client catches a CORBA system exception under these circumstances, it does not know whether it was caused by a problem with the ORB or a problem with the servant implementation.

To avoid confusion between ORB problems and servant problems, you should avoid directly throwing most CORBA system exceptions. Instead, you should throw userdefined exceptions to indicate application-level errors. This implies that you should consider all potential errors when designing your IDL interfaces so that you declare the appropriate exceptions within each operation's raises clause. Of course, not all error conditions fit clearly into one case or the other. For example, you should probably throw

338

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

the CORBA::NO_MEMORY system exception if you experience a memory allocation failure, simply because the meaning of that exception is unambiguous.

One CORBA system exception that applications are expected to throw is the CORBA::OBJECT_NOT_EXIST exception. As you will see in Chapter 11, server applications, and not the object adapter, often determine whether or not a given CORBA object exists. If the object adapter dispatches a request to an object that no longer exists, the application is expected to signify this by raising the OBJECT_NOT_EXIST exception.

9.8.3 Managing Memory with Exceptions

When a servant method throws an exception, the ORB releases any memory it allocated for any in and inout parameters, ignores all out and return values, and marshals the exception for return to the client. The servant method must therefore be careful to deallocate any memory it has already allocated for out parameters or return values; otherwise, that memory will be leaked.

Consider the following Foo::op operation, which takes an in object reference of type SomeObject and also has both an out and return variable-length structure:

exception SomeException {};

interface SomeObject {

string string_op() raises(SomeException);

};

struct Vls { long l_mem; string s_mem;

};

interface Foo {

Vls op(in SomeObject obj, out Vls vls_out) raises(SomeException);

};

Our servant method uses the SomeObject object reference to invoke string_op so that it can use its return value to initialize the string members of the out and return structures. One obvious way to implement Foo::op so that it doesn't leak if an exception is thrown by string_op is to enclose all invocations of that operation in a try block:

Vls * Foo_impl::

op(SomeObject_ptr obj, Vls_out vls_out) throw(CORBA::SystemException, SomeException)

{

vls_out = 0;

339

// rethrow exception

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

Vls * result = 0;

try {

//Create and initialize vls_out. vls_out = new Vls; vls_out->l_mem = 1234;

vls_out->s_mem = obj->string_op();

//Create and initialize return value. result = new Vls;

result->l_mem = 5678; result->s_mem = obj->string_op();

}

catch (const CORBA::Exception &) { delete vls_out.ptr();

delete result; throw;

}

return result;

}

First, we set the vls_out parameter and the result pointer to null to ensure that we can later delete them safely. (Later, either they will still be null or we will have assigned pointers to dynamically allocated instances to them.) We then enter the try block and create the out instance, using the return value of string_op to initialize the string member of the out structure. Similarly, we then create and initialize our return value. Our catch block is set up to catch CORBA::Exception, the base class for all CORBA user-defined and system exceptions. If string_op throws either an instance of the user-defined SomeException type or any CORBA system exception, the code in our catch block will delete both the out instance and the return value and rethrow the caught exception.

Wrapping all calls in try blocks certainly works, but it can be tedious. An easier way to deal with the problem is to use Stroustrup's "resource acquisition is initialization" idiom [39] and use C++ objects to clean up if an exception occurs. The following example shows how our dynamically allocated structures (the acquired resources) can be managed by creating Vls_var instances to look after them (the initialization):

Vls * Foo_impl::

op(SomeObject_ptr obj, Vls_out vls_out) throw(CORBA::SystemException, SomeException)

{

//Create and initialize temporary out parameter. Vls_var temp_out = new Vls;

temp_out->l_mem = 1234; temp_out->s_mem = obj->string_op();

//Create and initialize return value.

Vls_var result = new Vls; result->l_mem = 5678;

340