使用 SWIG 制作 Java 共享库的 SIGSEGV 错误

2022-01-12 00:00:00 swig segmentation-fault java nfc

所以,我正在尝试使用 SWIG 将 C 库 (libnfc) 移植到 Java.

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

我已经到了编译共享库的地步,一个基本的nfc_version()"方法调用就可以了.但是,调用nfc_init()"进行设置会导致 SIGSEGV 错误.直接调用nfc库就可以了.

I've got to the point of having a compiled shared library, and a basic "nfc_version()" method call will work. However, calling "nfc_init()" to set things up causes a SIGSEGV error. Calling the nfc library directly is fine.

我用来生成共享库的命令:

The commands I used to generate the shared library:

swig -java -I../libnfc/include nfclib.i 
gcc -c -I/usr/lib/jvm/java-7-openjdk-i386/include/ -I/usr/lib/jvm/java-7-openjdk-i386/include/linux nfclib_wrap.c
gcc -shared nfclib_wrap.o ../build/libnfc/libnfc.so libnfc_wrap.so

libnfc.i 文件:

The libnfc.i file:

%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>

即它应该包括 libnfc 提供的所有方法.

I.e. it should include all the methods that libnfc provides.

这是我得到的错误日志:http://openetherpad.org/AyVDsO4XTg

Here is the error log I am getting: http://openetherpad.org/AyVDsO4XTg

显然,从我提供的信息中可能无法找到特定的解决方案.但是任何关于尝试的事情的建议都会非常感激(我在这里的知识已经结束了).

Obviously, it may be that a specific solution may not be available from the information I have provided. But any suggestions of things to try would be really appreciated (I'm sort of at the end of my knowledge here).

推荐答案

要始终自动将相同的指针传递给函数,这在 SWIG 中相当简单.例如,给定头"文件 test.h,它捕获了问题的核心部分:

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
", *ctx); }
void release_context(struct context *ctx) { printf("Delete: %p
", ctx); free(ctx); }

void foo(struct context *ctx) { printf("foo: %p
", 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"

这会为 struct context *ctx 设置一个类型映射,而不是从 Java 中获取输入,而是在匹配的任何地方自动调用 get_global_ctx().

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

这可能足以让 Java 开发人员使用一个健全的接口,但它并不理想:它强制上下文是全局的,这意味着没有 Java 应用程序可以同时使用多个上下文.

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.

鉴于 Java 是一种 OO 语言,一个更好的解决方案是使上下文成为第一类对象.我们也可以让 SWIG 为我们生成这样的接口,尽管它有点复杂.我们的 SWIG 模块文件变为:

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.
  }
}

给予:

Init: 0xb66dab40
Delete: 0xb66dab40

在动态类型语言中,这将是最难完成的部分——我们可以使用一种或另一种形式的元编程来根据需要插入成员函数.因此,我们可以完全按照预期说出类似 new context().foo(); 的内容.Java 是静态类型的,所以我们需要更多的东西.我们可以通过多种方式在 SWIG 中执行此操作:

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. 接受我们现在可以非常高兴地调用 test.foo(new context()); - 它看起来很像 Java 中的 C,所以我建议它可能是如果你最终编写了大量看起来像 C 的 Java,代码味道会很糟糕.

  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.

使用 %extend (手动)将方法添加到上下文类中,test.i 中的 %extend 变为:

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);
    }
  }

  • %extend 一样,但在 Java 端使用 typemap 编写胶水:

  • 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)

    请注意,我在这里没有向 SWIG 展示我的上下文结构的真正定义 - 它始终在需要真正定义的任何地方都遵循我的库",因此不透明指针保持完全不透明.

    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.

    用双指针包装 init_context 的更简单的解决方案是使用 %inline 提供一个仅在包装器中使用的额外函数:

    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"
    

    足以让我们编写如下Java:

    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);
      }
    }
    

    替代但类似的方法包括使用 cpointer.i 库:

    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"
    

    然后您可以将其用作:

    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);
      }
    }
    

    还有一个 pointer_class 宏,它比这更面向对象,可能值得使用.关键是您提供了工具来处理 SWIG 用来表示它一无所知的指针的不透明指针对象,但避免了本质上颠覆类型系统的 getCPtr() 调用.

    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.

  • 相关文章