From d73f04e925e9210f2c688ef3656e6059eb540565 Mon Sep 17 00:00:00 2001
From: William S Fulton
With directors routing method calls to Java, and proxies routing them
-to C++, the handling of exceptions is an important concern. In Swig
-2.0, the director class methods ignored java exceptions that occurred
-during method calls dispatched to the Java director class and simply
-returned '$null' to the C++ caller. The default behavior now throws a
-Swig-defined DirectorException
C++ exception. A facility
-is now provided to allow translation of thrown Java exceptions into
-C++ exceptions. This can be done in two different ways using
-the %feature("director:except")
directive. In the
-simplest approach, a code block is attached to each director method to
-handle the mapping of java exceptions into C++ exceptions.
+to C++, the handling of exceptions is an important concern.
+The default behavior from SWIG 3.0
+onwards is to convert the thrown Java exception into a SWIG defined
+DirectorException
C++ exception.
+SWIG 2.0 and earlier versions didn't provide any mechanism to handle the Java director method exceptions in C++.
+
+Converting Java exceptions into C++ exceptions can be done in two different ways using
+the director:except
feature.
+In the simplest approach, a code block is attached to each director method to
+handle the mapping of Java exceptions into C++ exceptions.
-// All the rules to associate a feature with an element apply %feature("director:except") MyClass::method(int x) { - jthrowable $error = jenv->ExceptionOccurred(); + jthrowable $error = jenv->ExceptionOccurred(); if ($error) { - jenv->ExceptionClear(); - if (Swig::ExceptionMatches(jenv,$error,"java/lang/IndexOutOfBoundsException")) - throw std::out_of_range(Swig::JavaExceptionMessage(jenv,$error).message()); - else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException")) - throw MyCppException(Swig::JavaExceptionMessage(jenv,$error).message()); - else - throw std::runtime_error("Unexpected exception thrown by MyClass::method"); + jenv->ExceptionClear(); + if (Swig::ExceptionMatches(jenv, $error, "java/lang/IndexOutOfBoundsException")) + throw std::out_of_range(Swig::JavaExceptionMessage(jenv, $error).message()); + if (Swig::ExceptionMatches(jenv, $error, "$packagepath/MyJavaException")) + throw MyCppException(Swig::JavaExceptionMessage(jenv, $error).message()); + throw std::runtime_error("Unexpected exception thrown in MyClass::method"); } } class MyClass { - void method(int x); /* on C++ side, may get std::runtime_error or MyCppException */ + /** Throws either a std::out_of_range or MyCppException on error */ + void method(int x); }
-This approach allows mapping java exceptions thrown by director methods into
-C++ exceptions, to match the exceptions expected by a C++ caller. There
-need not be any exception specification on the method. This approach gives
-complete flexibility to map exceptions thrown by a java director
-implementation into desired C++ exceptions. The
-function Swig::ExceptionMatches
+This approach allows a flexible mapping of Java exceptions thrown by director methods into
+C++ exceptions expected by a C++ caller. There
+need not be any C++ exception specifications on the C++ method. The
+utility function Swig::ExceptionMatches
and class Swig::JavaExceptionMessage
are provided to simplify
-writing code for wrappers that use director:except
feature. These simplify
-testing the type of the java exception and constructing C++ exceptions. The
+writing code for wrappers that use the director:except
feature. The
function Swig::ExceptionMatches
matches the type of the
jthrowable
thrown against a fully qualified JNI style class
-name, like "java/lang/IOError"
. If the throwable class is the same
-type, or derives from the given type, it returns true. Care must be taken to
+name, such as "java/lang/IOError"
. If the throwable class is the same
+type, or derives from the given type, Swig::ExceptionMatches
will return true. Care must be taken to
provide the correct fully qualified name, since for wrapped exceptions the
generated proxy class will have additional package qualification, depending on
-the '-package' argument and use of nspace
-feature. The variable $error is simply a unique variable name to allow
-assignment of the exception that occurred. The variable $packagepath is
-replaced by the outer package provided for swig generation by the -package
-option. The class Swig::JavaExceptionMessage
is a holder
-object giving access to the message from the thrown java exception.
-The message() method returns the exception message as a const char *
,
+the '-package' argument and use of the nspace
+ feature. The special variable $error
is expanded by SWIG into a unique variable name and
+should be used for the
+assignment of the exception that occurred. The special variable $packagepath
is
+replaced by the outer package provided for SWIG generation by the -package
+option. The utility class Swig::JavaExceptionMessage
is a holder
+providing access to the message from the thrown Java exception.
+The message()
method returns the exception message as a const char *
,
which is only valid during the lifetime of the holder. Any code using this message
needs to copy it, for example into a std::string or a newly constructed C++ exception.
-If many methods may throw different exceptions, using this approach to
-write handlers for a large number of methods will result in
-duplication of the code in the director:except
feature
-code blocks, and will require separate feature definitions for every
-method. So an alternative approach is provided, using typemaps in a
+Using the above approach to
+write handlers for a large number of methods will require
+repetitive duplication of the director:except
feature code.
+To mitigate this, an alternative approach is provided via typemaps in a
fashion analagous to
the "throws" typemap. The
"throws" typemap provides an approach to automatically map all the C++
exceptions listed in a method's defined exceptions (either from
-an exception specification or a %catches
-feature) into Java exceptions, for the generated proxy classes. To
-provide the inverse mapping, the directorthrows
typemap
-is provided.
+a C++ exception specification or a %catches
+feature) into Java exceptions.
+The "directorthrows" typemap provides the inverse mapping and should contain
+code to convert a suitably matching Java exception into a C++ exception.
+The example below converts a Java java.lang.IndexOutOfBoundsException
exception
+to the typemap's type, that is std::out_of_range
:
-
Using directorthrows typemaps allows a
-generic director:except
feature to be combined with
-method-specific handling to achieve the desired result. The
-default director:except
feature, in combination
-with directorthrows
typemaps generate exception mapping
-to C++ exceptions for all the exceptions defined for each method. The
-default definition is shown below.
+%typemap(directorthrows) std::out_of_range %{ + if (Swig::ExceptionMatches(jenv, $error, "java/lang/IndexOutOfBoundsException")) { + throw std::out_of_range(Swig::JavaExceptionMessage(jenv, $error).message()); + } +%} ++
+The "directorthrows" typemap is then used in conjunction with the
+director:except
feature if the $directorthrowshandlers
special variable
+is used in the feature code. Consider the following, which also happens to be the default:
+
%feature("director:except") %{ - jthrowable $error = jenv->ExceptionOccurred(); + jthrowable $error = jenv->ExceptionOccurred(); if ($error) { - jenv->ExceptionClear(); + jenv->ExceptionClear(); $directorthrowshandlers throw Swig::DirectorException(jenv, $error); } @@ -3657,34 +3666,66 @@ default definition is shown below.
The code generated using the director:except
feature
-replaces the $directorthrowshandlers
with code that throws
-appropriate C++ exceptions from directorthrows
typemaps
-for each exception defined for the method. Just as with
-the "throws" typemap, the
-possible exceptions may be defined either with an exception
-specification ( throw(MyException,std::runtime_error)
) or
-using the %catches
feature applied to the method.
$directorthrowshandlers
special variable with the code in
+the "directorthrows" typemaps, for each and every exception defined for the method.
+The possible exceptions can be defined either with a C++ exception
+specification or %catches
as described for the
+"throws" typemap.
+
+
++Consider the following director method: +
+ ++ ... + virtual void doSomething(int index) throw (std::out_of_range); + ... ++
Note: Using the %catches feature to define the -handled exceptions is preferred to using exception specifications. If -the interface is defined with an exception specification the generated -swig proxy classes will have the same exception specification. In C++ -if exceptions other than those in the specification are thrown, the -program will be terminated.
+
+When combined with the default director:except
feature and the "directorthrows" typemap above,
+the resulting code generated in the director method after calling up to Java will be:
+
Because this default definition maps any unhandled java exceptions to -Swig::DirectorException, any director methods that define exception -specifications will cause program termination. To simply ignore -unexpected exceptions, the default can be changed to: +
+jthrowable swigerror = jenv->ExceptionOccurred(); +if (swigerror) { + jenv->ExceptionClear(); + if (Swig::ExceptionMatches(jenv, swigerror, "java/lang/IndexOutOfBoundsException")) { + throw std::out_of_range(Swig::JavaExceptionMessage(jenv, swigerror).message()); + } + + throw Swig::DirectorException(jenv, swigerror); +} ++
+Note: Beware of using exception specifications as the SWIG director methods +will be generated with the same exception specifications and if the +director method throws an exception that is not specified it is likely +to terminate your program. See the C++ standard for more details. +Using the %catches feature instead to define the handled exceptions does not suffer +this potential fate. +
+ +Because the default code generation maps any unhandled Java exceptions to
+Swig::DirectorException
, any director methods that have exception
+specifications may cause program termination. To simply ignore
+unexpected exceptions, the default handling can be changed with:
%feature("director:except") %{ - jthrowable $error = jenv->ExceptionOccurred(); + jthrowable $error = jenv->ExceptionOccurred(); if ($error) { - jenv->ExceptionClear(); + jenv->ExceptionClear(); $directorthrowshandlers - return $null; + return $null; // exception is ignored } %}@@ -3692,27 +3733,25 @@ unexpected exceptions, the default can be changed to:
Alternatively an exception compatible with the existing director
-method exception specifications may be thrown. Assuming that all
+method exception specifications can be thrown. Assuming that all
methods allow std::runtime_error to be thrown,
the return $null;
could be changed to:
+
- throw std::runtime_error(Swig::JavaExceptionMessage(jenv,$error).message()); + throw std::runtime_error(Swig::JavaExceptionMessage(jenv, $error).message());
In more complex situations, a separate director:except
feature
may need to be attached to specific methods.
Below is a complete example demonstrating the use
-of directorthrows
typemaps. The directorthrows
typemap
-provides a code fragment to test for a pending java exception type, and the
-resulting C++ exception that will be thrown. In this example, a
-generic directorthrows typemap is appropriate for all three exceptions - all
-take single string constructors. If the constructors had different constructors,
+of the "directorthrows" typemaps. In this example, a
+generic "directorthrows" typemap is appropriate for all three exceptions - all
+take single string constructors. If the exceptions had different constructors,
it would be neccessary to have separate typemaps for each exception type.
@@ -3721,22 +3760,29 @@ it would be neccessary to have separate typemaps for each exception type.
example interface that could be generated and built. -->
+%module(directors="1") example + +%{ + #include <string> + #include <stdexcept> +%} + // Define exceptions in header section using std::runtime_error %define DEFINE_EXCEPTION(NAME) %{ - #include <exception> namespace MyNS { - struct NAME : public std::runtime_error { NAME(const std::string& what):runtime_error(what) {}; }; + struct NAME : public std::runtime_error { NAME(const std::string &what) : runtime_error(what) {} }; } %} %enddef -// Expose c++ exceptions as java Exceptions with getMessage + +// Expose C++ exceptions as Java Exceptions by changing the Java base class and providing a getMessage() %define DECLARE_EXCEPTION(NAME) %typemap(javabase) MyNS::NAME "java.lang.Exception"; -%rename(getMessage,fullname=1) MyNS::NAME::what; +%rename(getMessage) MyNS::NAME::what; namespace MyNS { struct NAME { - NAME(const std::string& what); + NAME(const std::string& what); const char * what(); }; } @@ -3744,17 +3790,16 @@ namespace MyNS { DEFINE_EXCEPTION(ExceptionA) DEFINE_EXCEPTION(ExceptionB) -DEFINE_EXCEPTION(Unknown) +DEFINE_EXCEPTION(Unexpected) -// Mark three methods to map director-thrown exceptions. -// Standard rules for feature matching apply +// Mark three methods to map director thrown exceptions. %feature("director:except") MyClass::meth1(int); %feature("director:except") MyClass::meth2; %feature("director:except") meth3; %typemap(directorthrows) MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected %{ - if (Swig::ExceptionMatches(jenv,$error,"$packagepath/$javaclassname")) - throw $1_type(Swig::JavaExceptionMessage(jenv,$error).message()); + if (Swig::ExceptionMatches(jenv, $error, "$packagepath/$javaclassname")) + throw $1_type(Swig::JavaExceptionMessage(jenv, $error).message()); %} DECLARE_EXCEPTION(ExceptionA) @@ -3769,68 +3814,54 @@ DECLARE_EXCEPTION(Unexpected) virtual void meth1(int x) throw(MyNS::ExceptionA, MyNS::ExceptionB) = 0; virtual void meth2() = 0; /* throws MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected */ virtual void meth3(float x) throw(MyNS::Unexpected) = 0; - virtual ~MyClass() {}; + virtual ~MyClass() {} }; }
-In this case the three different directorthrows
typemaps will be used
+In this case the three different "directorthrows" typemaps will be used
to generate the three different exception handlers for
meth1
, meth2
and meth3
. The generated
handlers will have "if" blocks for each exception type specified, in
-the exception specification or %catches
feature. The code block
-in the directorthrows typemap should always throw a c++ exception.
+the exception specification or %catches
feature.
Note that the directorthrows
typemaps are important
+
Note that the "directorthrows" typemaps are important
only if it is important for the the exceptions passed through the C++
layer to be mapped to distinct C++ exceptions. If director methods
are being called by C++ code that is itself wrapped in a
-Swig-generated java wrapper and access is always through this wrapper,
-the default Swig::DirectorException class provides enough information
+SWIG generated Java wrapper and access is always through this wrapper,
+the default Swig::DirectorException
class provides enough information
to reconstruct the original exception. In this case removing the
-$directorthrowshandlers replacement variable from the
+$directorthrowshandlers
special variable from the
default director:except
feature and simply always
-throwing a Swig::DirectorException will achieve the desired result.
+throwing a Swig::DirectorException
will achieve the desired result.
Along with this a generic exception feature is added to convert any
caught Swig::DirectorException
s back into the underlying
-java exceptions, for each method which may get a generic
-DirectorException from a wrapped director method.
Swig::DirectorException::raiseJavaException
method,
+as demonstrated with %javaexception
below:
+
-%feature ("except",throws="Exception") MyClass::myMeth %{ - try { $action } - catch (Swig::DirectorException & direxcp) { - // jenv always available in JNI code - // raise the java exception that originally caused the DirectorException - direxcp.raiseJavaException(jenv); +%javaexception("Exception") MyClass::myMethod %{ + try { + $action + } catch (Swig::DirectorException &e) { + // raise/throw the Java exception that originally caused the DirectorException + e.raiseJavaException(jenv); return $null; } %}
The throws="Exception"
attribute on the exception
-feature is necessary if any of the translated exceptions will be
-checked exceptions, since the java compiler will otherwise assert that
-no checked exceptions can be thrown by the method. This may be more
-specific that the completely generic "Exception" class, of course. A
-similar feature must be added to director methods to allow checked
-exceptions to be thrown from the director method implementations.
-Here, no actual exception handling is needed - the feature simply
-is being used to add a generic checked exception signature to the
-generated director method wrapper.
-%feature ("except",throws="Exception") MyDirectorClass::myDirMeth %{ %} --
+See the Exception handling with %exception and %javaexception +section for more on converting C++ exceptions to Java exceptions. +
-$jniinput, $javacall and $packagepath
+$error, $jniinput, $javacall and $packagepath
These special variables are used in the directors typemaps. See Director specific typemaps for details.
$javaclassname
If the -package commandline option is not used to specify the package, then '$packagepath/' will be removed from the resulting output JNI field descriptor.
Do not forget the terminating ';' for JNI field descriptors starting with 'L'.
If the ';' is left out, Java will generate a "method not found" runtime error.
+Note that the $packagepath
substitution always uses the path separator '/' when expanded.
+The $javaclassname
expansion can be confusing as it is normally expanded using the '.' separator.
+However, $javaclassname
is expanded using the path separator '/' in typemap's "descriptor" attribute
+as well as in the "directorthrows" typemap.
%typemap(directorthrows)
+
+Conversion of Java exceptions to C++ exceptions in director method's exception handling.
+This typemap is expected to test the $error special variable for a matching Java exception
+and if successful convert and throw it into a C++ exception given by the typemap's type.
+The $error
special variable is of type jthrowable
and is
+substituted with a unique variable name in the generated code.
+
+The example below converts a Java java.lang.IndexOutOfBoundsException
exception
+to the typemap's type, that is std::out_of_range
:
+
+%typemap(directorthrows) std::out_of_range %{ + if (Swig::ExceptionMatches(jenv, $error, "java/lang/IndexOutOfBoundsException")) { + throw std::out_of_range(Swig::JavaExceptionMessage(jenv, $error).message()); + } +%} ++
+The utility function Swig::ExceptionMatches
+and class Swig::JavaExceptionMessage
are helpers available when using directors and are described
+in the Java Exceptions from Directors section.
+
%typemap(javapackage)