SIGSEGV error using SWIG to make a java shared library

后端 未结 2 1198
别那么骄傲
别那么骄傲 2021-01-06 16:56

So, I\'m trying to port a C library (libnfc) to Java using SWIG.

I\'ve got to the point of having a compiled shared library, and a basic \"nfc_version()\" method cal

相关标签:
2条回答
  • 2021-01-06 17:17

    So Flexo's answer is the proper way to solve this problem but SWIG also offers the "cpointer.i" module (described here: http://www.swig.org/Doc1.3/SWIGDocumentation.html#Library_nn3) which allowed me to hack together a quick solution so that I could test I had the basic library working. I thought i would put in this answer just for completeness and offer an alternative to anyone who stumbles across this question.

    The need for this is because of what I would describe as an asymmetry in what is generated by SWIG. A base type object has a property, swigCPtr, which is the memory address of that object (a "self pointer"). Then to make a pointer, you just get swigCptr from the base type and pass it in the constructor for the pointer type (SWIGTYPE_p_X) that swig generated. BUT the pointer type ONLY exists in Java and just holds the swigCptr value. There is no 'c' block of memory holding that pointer. Hence, there is no equivalent of the swigCPtr in the pointer type. Ie there is no self pointer stored in the pointer type, in the same way that there IS a self pointer stored in the base type.

    So you can't make a pointer to a pointer (SWIGTYPE_p_p_X) as you dont have an address of the pointer to pass it when constructing it.

    My new '.i' file is as follows:

    %module nfc
    %{
    #include <nfc/nfc.h>
    #include <nfc/nfc-types.h>
    #include <nfc/nfc-emulation.h>
    %}
    
    %include <nfc/nfc.h>
    %include <nfc/nfc-types.h>
    %include <nfc/nfc-emulation.h>
    
    %include "cpointer.i"
    %pointer_functions(nfc_context*, SWIGTYPE_p_p_nfc_context)
    

    What this last macro does is provide 4/5 functions for making pointers. The functions all take and return the types that swig should already have generated. The new The usage in Java to get the nfc_init and nfc_open commands to work:

    SWIGTYPE_p_p_nfc_context context_p_p = nfc.new_SWIGTYPE_p_p_nfc_context();
    nfc.nfc_init(context_p_p);
    //get the context pointer after init has set it up (the java doesn't represent what's happening in the c) 
    SWIGTYPE_p_nfc_context context_p = nfc.SWIGTYPE_p_p_nfc_context_value(context_p_p);
    SWIGTYPE_p_nfc_device pnd = nfc.nfc_open(context_p, null);
    

    notice that I have to get the pointer from the double pointer after the init command has completed, as the information stored in the java pointer object is separated from the C 'world'. So retrieving the 'value' of context_p_p will give a context_p with the correct value of its pointer.

    0 讨论(0)
  • 2021-01-06 17:20

    To always pass the same pointer in to a functions automatically it's fairly straightforward in SWIG. For example given the "header" file test.h, which captures the core part of your problem:

    struct context; // only used for pointers
    
    void init_context(struct context **ctx) { *ctx=malloc(1); printf("Init: %p\n", *ctx); }
    void release_context(struct context *ctx) { printf("Delete: %p\n", ctx); free(ctx); }
    
    void foo(struct context *ctx) { printf("foo: %p\n", ctx); }
    

    We can wrap it and automatically cause a global context to be passed in everywhere one is expected by doing something like:

    %module test
    
    %{
    #include "test.h"
    
    // this code gets put in the generated C output from SWIG, but not wrapped:
    static struct context *get_global_ctx() {
      static struct context *ctx = NULL;
      if (!ctx) 
        init_context(&ctx);
      return ctx;
    }
    %}
    
    %typemap(in,numinputs=0) struct context *ctx "$1=get_global_ctx();"
    
    %ignore init_context; // redundant since we call it automatically
    
    %include "test.h"
    

    This sets a typemap for struct context *ctx that instead of taking an input from Java automatically calls get_global_ctx() everywhere it matches.

    That's probably sufficient to make a sane-ish interface for a Java developer to use, however it's less than ideal: it forces the context to be a global and means that no Java application can ever work with multiple contexts at once.

    A nicer solution, given that Java is an OO language, is to make the context become a first class Object. We can also make SWIG generate such an interface for us although it's a little more convoluted. Our SWIG module file becomes:

    %module test
    
    %{
    #include "test.h"
    %}
    
    // These get called automatically, no need to expose:
    %ignore init_context;
    %ignore delete_context;
    
    // Fake struct to convince SWIG it should be an object:
    struct context {
      %extend {
        context() {
          // Constructor that gets called when this object is created from Java:
          struct context *ret = NULL;
          init_context(&ret); 
          return ret;
        }
        ~context() {
          release_context($self);
        }
      }
    };
    
    %include "test.h"
    

    and we can exercise this code successfully:

    public class run {
      public static void main(String[] argv) {
        System.loadLibrary("test");
        context ctx = new context();
        // You can't count on the finalizer if it exits:
        ctx.delete();
        ctx = null;
        // System.gc() might also do the trick and in a longer
        // running app it would happen at some point probably.
      }
    }
    

    gives:

    Init: 0xb66dab40
    Delete: 0xb66dab40
    

    In a dynamically typed language that would be the hard part done - we could use meta programming of one form or another to insert a the member functions as needed. Thus we would be able to say something like new context().foo(); entirely as expected. Java is statically typed though so we need something more. We can do this in SWIG in a number of ways:

    1. Accept that we can now call test.foo(new context()); quite happily - it looks a lot like C in Java still though so I'd suggest it might be a code smell if you end up writing lots of Java that looks like C.

    2. Use %extend to (manually) add the methods into the context class, the %extend in test.i becomes:

      %extend {
          context() {
            // Constructor that gets called when this object is created from Java:
            struct context *ret = NULL;
            init_context(&ret); 
            return ret;
          }
          ~context() {
            release_context($self);
          }
          void foo() {
            foo($self);
          }
        }
      
    3. As with %extend, but write the glue on the Java side, using a typemap:

      %typemap(javacode) struct context %{
        public void foo() {
          $module.foo(this);
        }
      %}
      

      (Note: this needs to be early enough in the interface file to work)

    Notice that nowhere here have I shown SWIG the real definition of my context struct - it always defers to my "library" for anything where the real definition is required, thus the opaque pointer remains complete opaque.


    A simpler solution to wrap the init_context with a double pointer would be to use %inline to provide an extra function that is only used in the wrapper:

    %module test
    
    %{
    #include "test.h"
    %}
    
    %inline %{
      struct context* make_context() {
        struct context *ctx;
        init_context(&ctx);
        return ctx;
      }
    %}
    
    %ignore init_context;
    
    %include "test.h"
    

    Is sufficient to allow us to write the following Java:

    public class run {
      public static void main(String[] argv) {
        System.loadLibrary("test");
        // This object behaves exactly like an opaque pointer in C:
        SWIGTYPE_p_context ctx = test.make_context();
        test.foo(ctx);
        // Important otherwise it will leak, exactly like C
        test.release_context(ctx);
      }
    }
    

    Alternative, but similar approaches would include using the cpointer.i library:

    %module test
    
    %{
    #include "test.h"
    %}
    
    %include <cpointer.i>
    
    %pointer_functions(struct context *,context_ptr);
    
    %include "test.h"
    

    Which you can then use as:

    public class run {
      public static void main(String[] argv) {
        System.loadLibrary("test");
        SWIGTYPE_p_p_context ctx_ptr = test.new_context_ptr();
        test.init_context(ctx_ptr);
        SWIGTYPE_p_context ctx = test.context_ptr_value(ctx_ptr);
        // Don't leak the pointer to pointer, the thing it points at is untouched
        test.delete_context_ptr(ctx_ptr);
        test.foo(ctx);
        // Important otherwise it will leak, exactly like C
        test.release_context(ctx);
      }
    }
    

    There's also a pointer_class macro that is a little more OO than that and might be worth using instead. The point though is that you're providing the tools to work with the opaque pointer objects that SWIG uses to represent pointers it knows nothing about, but avoiding the getCPtr() calls which are inherently subverting the type system.

    0 讨论(0)
提交回复
热议问题