1

How would you configure the SWIG .i file to handle the C FILE * type? The below function sets a file so that log output can be written to it. I need to call if from a Java class. Currently a public static void setLogFile(SWIGTYPE_p_FILE fd) function is generated by SWIG when I just include the C header file with the below function. Any ideas?

C Function:

void setLogFile(FILE *fd);

I attempted #1 using the below and got the below exception:

test.i:

%module Example
%{
#include "headerLogFile.h"
%}

%inline %{
void setLogFile(const char *fn) {
  FILE *f = fopen(fn, "w");
  setLogFile(f);
}
%}
%ignore setLogFile; 
%include "headerLogFile.h"

Exception:

[exec] test_wrap.c:193: error: conflicting types for 'setLogFile'
[exec] /test/include/headerLogFile.h:96: error: previous declaration of 'setLogFile' was here
[exec] test_wrap.c: In function `setLogFile':
[exec] test_wrap.c:195: warning: passing arg 1 of `setLogFile' from incompatible pointer type
c12
  • 9,557
  • 48
  • 157
  • 253

1 Answers1

2

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:

  1. We want the input to the actual public part of the module to be java.io.FileOutputStream.
  2. The the Java side of the JNI code is going to take a java.io.FileDescriptor instead however.
  3. The C++ side of the JNI code is going to see this as a jobject
  4. 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
  5. 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
  6. 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)
Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • I tried #1 first and it seems like it still finds the other setLogFile function even though I attempt to hide it. I updated the question with the exact SWIG file and exception if that helps...I have been able to hide other functions using the %ignore – c12 Nov 30 '11 at 17:47
  • @c12 - the problem there is you're doing something in C which would be an overload in C++, but C doesn't permit overloads. You probably want to give the `%inline` version of `setLogFile` a different name and then use either `%rename` with it or simply add some code on the Java side that forwards it to whatever the `%inline` version is now called. – Flexo Nov 30 '11 at 17:53