【问题标题】:SIGSEGV error using SWIG to make a java shared library使用 SWIG 制作 Java 共享库的 SIGSEGV 错误
【发布时间】:2013-01-24 06:52:23
【问题描述】:

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

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

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

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 文件:

%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 提供的所有方法。

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

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

【问题讨论】:

  • 你说你正在调用nfc_init - 你从哪里得到它需要调用它的SWIGTYPE_p_nfc_context?如果您只是在 Java 中编写 new SWIGTYPE_p_nfc_context(),它将生成一个指向任何内容的 指针,这可能会解释您的段错误。
  • (Java 中的“自然”接口可能是让nfc_init 创建一个上下文并返回它。逻辑步骤将适用于所有以nfc_context 作为它们的函数第一个参数是与最初传入的参数一起返回的上下文上的方法。如果这是您想要做的,SWIG 可以为您生成该代码。或者,如果每个进程只能有一个上下文,那么Java 库可以为您自动处理,完全隐藏上下文参数)。我很乐意详细说明。
  • WRT 链接错误,您不需要向 SWIG 显示真正的 nfc_context。你通常如何在 libnfc 中分配?一旦你构建了包装器,就不需要任何额外的工作来“正常工作”。
  • 这是正确的,因为您永远不想从 Java 中查看结构内部。 SWIG 无法在没有帮助的情况下构建或删除一个,但这正是 %extend 所做的。
  • 代码中有一个陷阱——如果你有一个匿名对象(new SWIGTYPE_p_nfc_context() 这里)一旦对getCPtr 的调用完成并删除对象(释放它的内存),GC 可以将其识别为未使用在调用nfc_init 之前,它是一个指针)。因此,您指向指针的指针可能已经悬空。此外,由于getCPtr 总是返回一个int,我认为你很可能在那里也有一个意外的演员——我会尽量避免每次调用getCPtr,因为它太容易丢失类型信息。 (即转换为intptr_t

标签: java swig nfc segmentation-fault


【解决方案1】:

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

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

我们可以包装它,并通过执行以下操作自动将全局上下文传递到预期的任何地方:

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

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

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

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

我们可以成功地运行这段代码:

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 中执行此操作:

  1. 接受我们现在可以非常高兴地调用test.foo(new context()); - 尽管它看起来仍然很像 Java 中的 C,所以我建议如果你最终编写大量看起来像C.

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

    %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. %extend 一样,但在Java 端使用类型映射编写胶水:

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

    (注意:这需要在接口文件中足够早才能工作)

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


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

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

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 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() 调用。

【讨论】:

  • 哇。好的。谢谢 :) 这可能有点超出我的想象。生病需要有一个适当的通读。 nfc_init 只是我想从 libnfc 使用的众多功能中的第一个,而且我没有太多时间“正确”地做它。所以我很可能需要采用更简单、更少 OO 的方法。并将功能封装在实际的 java 中。
  • 好的,我已经看过了,并没有我最初想的那么糟糕。我不想做的是对这个库中的许多对象和函数重复这个过程,因为我正在尝试从这个库构建 up。仍然非常有用,我可能至少会为最常用的方法等实现它。如果你不介意,我会等待接受这个作为答案,直到我得到它的工作。希望今天应该完成:)
  • 有更简单的方法可以生成可用的 Java 接口,但在我看来它们丑陋的 Java。不过,如果您愿意,我会添加。我也有一个简洁的功能的想法,即使是静态类型也可以大大简化这一点。
  • @Sam - 我添加了另外两个示例,说明您可以在 Java 中包装相同的标头。对于用户来说,我的偏好始终是“让它感觉像一流的 Java”,即使这会使预先编写界面有点复杂,但这就是我最初跳过它们的原因。
  • 嗨,我接受了这个作为答案,因为它确实是一个非常好的答案,并提供了很多例子来说明如何继续制作一个漂亮的 java 接口。不幸的是,我无法实现这一点,因为我不得不停止在 Java 中的工作(似乎几乎不可能通过 swig 将回调传递给 java 函数......)
【解决方案2】:

所以 Flexo 的答案是解决此问题的正确方法,但 SWIG 还提供了“cpointer.i”模块(在此处描述:http://www.swig.org/Doc1.3/SWIGDocumentation.html#Library_nn3),它使我能够快速解决问题,以便我可以测试我是否拥有基本的图书馆工作。我想我只是为了完整性而提出这个答案,并为任何偶然发现这个问题的人提供一个替代方案。

之所以需要这样做,是因为我将其描述为 SWIG 生成的内容的不对称性。基类型对象有一个属性 swigCPtr,它是该对象的内存地址(“自指针”)。然后要创建一个指针,您只需从基本类型中获取 swigCptr 并将其传递给 swig 生成的指针类型 (SWIGTYPE_p_X) 的构造函数。但是指针类型只存在于 Java 中,并且只保存 swigCptr 值。没有保存该指针的“c”内存块。因此,指针类型中没有等效的 swigCPtr。即指针类型中不存储自指针,与基类型中存储自指针相同。

所以你不能创建一个指向指针 (SWIGTYPE_p_p_X) 的指针,因为你在构造它时没有指针的地址来传递它。

我的新 '.i' 文件如下:

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

最后一个宏的作用是提供 4/5 函数来生成指针。这些函数都接受并返回 swig 应该已经生成的类型。在 Java 中使 nfc_init 和 nfc_open 命令工作的新用法:

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

请注意,我必须在 init 命令完成之后从双指针获取指针,因为存储在 java 指针对象中的信息与 C 的“世界”是分开的。因此,检索 context_p_p 的“值”将给出一个带有正确指针值的 context_p。

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 2020-07-31
    • 2010-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多