Given test.h which looks like:
#include <stdio.h>
inline void setLogFile(FILE *fd) {
fprintf(fd, "Test\n");
fflush(fd);
}
I can see three approaches you might chose to take to wrapping this function:
Method 1 - Pass a String
from Java:
Expose a function to Java that expects a file name passed as a String
, not a FILE*
:
%module method1
%{
#include "test.h"
%}
%inline %{
void setLogFile(const char *fn) {
FILE *f = fopen(fn, "w");
setLogFile(f);
}
%}
This uses %inline
to instruct SWIG to wrap this function at the same time as defining it. If you still use %include "test.h"
then you'd probably want to hide the original version from SWIG.
Method 2 - Wrap more of stdio.h:
Wrap more than just setLogFile
, wrap things like fopen
, fmemopen
etc. as appropriate. (I don't like this solution much personally so I've not made an example for it)
Method 3 - Expose a Java interface that takes a FileOutputStream
:
%module method3
%{
#include "test.h"
#include <cassert>
%}
// 3:
%typemap(jni) FILE *fd "jobject"
// 1:
%typemap(jstype) FILE *fd "java.io.FileOutputStream"
// 2:
%typemap(jtype) FILE *fd "java.io.FileDescriptor"
// 4:
%typemap(in) (FILE *fd) {
jfieldID field_fd;
jclass class_fdesc;
int rawfd;
class_fdesc = jenv->FindClass("java/io/FileDescriptor");
assert(class_fdesc);
field_fd = jenv->GetFieldID(class_fdesc, "fd", "I");
assert(field_fd);
rawfd = jenv->GetIntField($input, field_fd);
$1 = fdopen(rawfd, "w");
// Add some code to throw a Java exception if $1 is NULL (i.e. error)
}
// 5:
%typemap(javain, pre=" retainFD = $javainput;",
throws="java.io.IOException") FILE *fd "$javainput.getFD()"
// 6:
%pragma(java) modulecode=%{
private static java.io.FileOutputStream retainFD;
%}
%include "test.h"
This does the following things:
- We want the input to the actual public part of the module to be
java.io.FileOutputStream
.
- The the Java side of the JNI code is going to take a
java.io.FileDescriptor
instead however.
- The C++ side of the JNI code is going to see this as a
jobject
- On the C++ side we're going to do something that's a little evil - read a private
int
field in the FileDescriptor
class (see here). This is probably not portable and reading private parts of classes is generally considered bad, but it allows us to get something we can pass to fdopen()
to get a FILE*
for the "real" call
- Mostly this typemap takes the
FileOutputStream
and calls getFD()
on it to get the FileDescriptor
object for it. It also adds an exception specification to match getFD()
and performs one other function which is part of the next point
- We need to be sure that Java won't garbage collect and finalize the
FileOutputStream
, which would close the file handle and invalidate our FILE*
. We do this by keeping a reference to the FileOutputStream
we were given in a private static
variable. The pre="...
of the previous typemap causes the most recent one to be retained until we change to another one. (If we do call setLogFile
twice it's OK and in fact good that we release our reference to the previous FileOutputStream
)