
William S Fulton wrote:
> Ben Webb wrote:
>> Is there any tidy way to handle exceptions during the
>> cleanup/destruction of arguments to wrapped methods (as opposed to
>> during the method calls themselves) ?
> I don't think I have any ideal solutions for you, but you could consider
> modifying the cleanup() method to make it exception free. Throwing an
> exception when cleaning up is a bit like throwing an exception in a
> destructor .... to be avoided.
Perhaps my general example was too general. ;) Let's say my Foo object
is a kind of ostream-like object, where do_something() writes a single
character to the stream. My adapter wraps a Python file-like object and
the do_something() C++ method in principle calls the Python object's
write() method to write that character. This is horribly inefficient, of
course, so do_something() actually writes straight into a C++ buffer,
and calls a cleanup() method when the buffer fills up - the cleanup()
method is now the only method that actually calls into Python. So before
I destroy my adapter, I have to be sure to call that cleanup() method to
empty the buffer so that the C++ and Python streams are synchronized.
Any Python method can throw a Python exception, so both do_something()
and cleanup() can throw corresponding C++ exceptions. In most cases,
however, the buffer is sufficiently large that the first time a Python
call is made is in the cleanup phase of the adapter object. So if I
swallowed any Python exception in cleanup(), the user would never hear
about IO errors (e.g. they tried to write to a file opened for read
only) or attribute/type errors (e.g. they passed a Python object that is
not file-like, or that has no write() method or the method has the wrong
signature). So I don't think an exception free cleanup method is an
option here.
> You can also take an "exception safe" route rather than using freearg
> typemaps to do the cleaning up by using a C++ wrapper around Foo and
> creating an instance of this as a temporary variable in the 'in'
> typemap.
Thanks - that is certainly tidier. But it doesn't work for me since
there seems to be no way to propagate any exception raised in cleanup()
back to Python.
Ben
--
ben@...
"It is a capital mistake to theorize before one has data."
- Sir Arthur Conan Doyle
Costas wrote:
>
>
> On Wed, May 27, 2009 at 9:13 PM, Costas Malamas <costas@...
> <mailto:costas@...>> wrote:
>
>
>
> On Tue, May 26, 2009 at 11:02 PM, William S Fulton
> <wsf@... <mailto:wsf@...>> wrote:
>
> Costas wrote:
>
> Hello all,
>
> I am trying to wrap a simple C++ class hierarchy in Java,
> using directors.
> The C++ is fairly trivial, but uses the clone() pattern,
> which is giving me
> trouble:
>
> class Base {
> ...
>
> Base(const Base& other)
> { ... };
>
> virtual Base* clone() {
> return Base(*this);
> }
>
> ...
> }
>
> class Extended : public Base {
> ...
>
> Extended(const Extended& other)
> { ... };
>
> virtual Base* clone() {
> return Extended(*this);
> }
>
> ...
> }
>
> Now, I can extend this using directors in Java, by just
> renaming clone() to
> instance() and using the %newobject directive for clone().
> The copy
> constructor works fine, but whenever my C++ uses clone() and
> goes to the
> Java version, I get access violation errors which crash the JVM.
>
> Has anybody tried a similar pattern? I've tried refactoring
> this a bunch of
> times that ended up messing up the design more than fixing
> it, so now I'd
> like to get the above (original) design working. My only
> half-baked
> suspicion is that the renaming of clone() may be still
> creating some
> conflict with Java's own clone() when called from C++.
>
> Any input or ideas are welcome and thanks in advance.
>
>
> Don't you get a warning about not using pointers for virtual
> director method return types? The memory handling is impossible
> to deal with if I remember right and is probably what you are
> suffering.
>
> William
>
>
> Honestly, I don't remember (the original design was multiple
> refactorings ago), but I don't think so. What I don't quite get is why
> if the director mechanism is smart/robust enough to deal with a copy
> constructor, it falls apart on clone(). And if the memory handling is
> this hairy what's the advisable/standard practice when doing dependency
> injection?
>
I've put together an example and can't get a seg fault. Can you modify
my example to demonstrate? The seg fault could just be your dodgy c++
code ;)
William
Ben Webb wrote:
> Is there any tidy way to handle exceptions during the
> cleanup/destruction of arguments to wrapped methods (as opposed to
> during the method calls themselves) ?
>
> See attached test.i for a full example, which can be run (at least on my
> Linux machine) with something like:
> swig -c++ -python test.i && g++ -shared -o _test.so test_wrap.cxx -fPIC
> -I/usr/include/python2.5 && echo "import test; test.mytest(1)" | python
>
> In this example, my C++ method expects to get a Foo object which
> provides a method do_something(). In my SWIG interface I provide an
> implementation of Foo which acts as an adapter, delegating to a wrapped
> Python object. (The Foo object is created and destroyed during the
> lifetime of the wrapping function, with in and freearg typemaps.) If
> do_something() raises an exception, all is OK, because I use %exception
> to catch that and raise an equivalent Python exception.
>
> But what if a cleanup action is required? (For example, Foo may buffer
> its actions to reduce the number of Python calls - for instance if
> do_something() is called millions of times - so a cleanup method is
> needed to flush the buffer, possibly called from the destructor.) In the
> example cleanup is called by the freearg typemap. This doesn't work
> because if cleanup throws an exception, it is not caught (plus freearg
> is called by SWIG_fail as well, and we probably shouldn't call cleanup()
> if the wrapped method failed).
>
> Ideally, I would like to call cleanup immediately after the method, so
> that test_wrap.cxx looks like
>
> {
> try {
> mytest(arg1);
> arg1->cleanup();
> } catch (...) {
> SWIG_exception(SWIG_RuntimeError, "Error caught");
> }
> }
>
> Unfortunately, there don't seem to be any typemaps
> ("cleanup_arg_after_method"?) which get inserted into the $action code
> and thus are wrapped by %exception.
>
> I could generate the desired code with %exception or %feature("action")
> but unfortunately these match on the method name (mytest) not on the
> type of the argument (Foo). So I'd have to write a bunch of these, one
> for every method that takes a Foo*, and keep everything in sync.
>
> Right now I handle things by abusing the argout typemap, with something like
> %typemap(argout) Foo* {
> try {
> $1->cleanup();
> } catch (...) {
> ...
> Py_DECREF($result);
> }
> }
> This is better than using freearg, because it is only called if the
> method succeeds, but it is a bit ugly because the result object has
> already been created, so we have to manually free that if the cleanup
> fails (plus, if there are other argout arguments before this one, they
> may have created other results, and argout arguments after this one
> won't be processed, potentially leading to a memory leak).
>
> Any other possible solutions?
There isn't any way to put code from a typemap between the call to the
wrapped method and before the 'out' typemap contents. Using %exception
is the only way, but as you say that needs specifying by method name and
not by input parameter type.
I don't think I have any ideal solutions for you, but you could consider
modifying the cleanup() method to make it exception free. Throwing an
exception when cleaning up is a bit like throwing an exception in a
destructor .... to be avoided.
You can also take an "exception safe" route rather than using freearg
typemaps to do the cleaning up by using a C++ wrapper around Foo and
creating an instance of this as a temporary variable in the 'in'
typemap. The following code ensures cleanup() is called and doesn't leak
memory. I'm not 100% sure, but this solution might meet all your needs.
%module test
%include "exception.i"
%{
class Foo {
PyObject *p_;
public:
Foo(PyObject *p) : p_(p) {}
void do_something() { throw 0; }
void cleanup() { throw 1.0; }
};
%}
%{
#include <iostream>
struct FooPythonWrapper {
Foo *pFoo;
FooPythonWrapper() : pFoo(0) {}
~FooPythonWrapper() {
if (pFoo) {
try {
pFoo->cleanup();
} catch (...) {
std::cout << "cleanup exception caught and swallowed" << std::endl;
}
}
delete pFoo;
}
};
%}
%exception {
try {
$action
} catch (...) {
SWIG_exception(SWIG_RuntimeError, "Error caught");
}
}
%typemap(typecheck) Foo * = PyObject *;
%typemap(in) Foo * (FooPythonWrapper fooWrapper) %{
// $1 = new Foo($input);
fooWrapper.pFoo = new Foo($input);
$1 = fooWrapper.pFoo;
%}
%inline %{
void mytest(Foo *f) {
f->do_something();
}
%}
William