SWIG (v1.3.29) 生成 C++ Java Vector 类无法正常运行

发布于 2024-12-16 11:02:26 字数 732 浏览 2 评论 0 原文

我有一些本机 C++ 代码,我正在使用 SWIG 将其转换为 Java,以便我的 Java 应用程序可以使用它。特别是有一些函数返回 std::vector。以下是我的接口文件的片段:

%include "std_vector.i"
namespace std {
  %template(Vector) vector<double>;
  %template(Matrix) vector<vector<double> >;
}

%include "std_string.i"

std_string.istd_vector.i 包含在我正在使用的 SWIG 构建中。我的第一个惊喜是 Java 输出包含 SWIG“自己”版本的 Vector 类(而不是使用 java.util.Vector)。我真正的问题是从这些函数返回的向量似乎不起作用。例如,我无法使用 get() (有时会使程序崩溃)或返回负值的 size() 函数检索它们的内容。我知道 Vector 包含数据,因为我编写了相同函数的“String”版本,这些函数只是迭代 Vector(回到本机 C++ 代码中)并返回以逗号分隔的 String 值中的内容。虽然这是一个有效的解决方法,但最终我希望它能够正常工作,让我能够接收和操作向量。任何帮助/提示将不胜感激。

I have some native C++ code that I'm converting to Java using SWIG so that my Java application can use it. In particular there are some functions that return std::vector. Here's a snippet of my interface file:

%include "std_vector.i"
namespace std {
  %template(Vector) vector<double>;
  %template(Matrix) vector<vector<double> >;
}

%include "std_string.i"

std_string.i and std_vector.i were included in my build of SWIG I'm using. My first surprise was that the Java output included SWIG's "own" version of the Vector class (as opposed to using java.util.Vector). My real issue is that the Vectors that get returned from these functions do not seem to work. For example I cannot retrieve their contents using get() (sometimes crashing the program) or the size() function returning negative values. I know the Vectors contain data because I coded 'String' versions of the same functions which simply iterate through the Vectors (back in the native C++ code) and return the contents in a comma separated String value. While this is a valid workaround, ultimately I would like this to work properly with me being able to receive and manipulate the Vectors. Any helps/tips would be much appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

沫尐诺 2024-12-23 11:02:26

在 Java 中包装 std::vector 的适当基类型是 java.util.AbstractList。使用 java.util.Vector 作为基础会很奇怪,因为您最终会得到两组存储,一组在 std::vector 中,一组在java.util.Vector

SWIG 不为您执行此操作的原因是因为您不能拥有AbstractList 在 Java 中,它必须是 AbstractListDouble 继承自 Object,而 double 是原始类型)。

说了这么多,我整理了一个小例子,其中包含 std::vectorstd::vector 。 > 在 Java 中表现得很好。它并不完整,但它支持 Java 中的“foreach”迭代风格以及元素上的 set()/get() 。它应该足以展示如何在您需要时实现其他事情。

我将分部分讨论接口文件,但基本上它都是连续且完整的。

从定义我们的模块 numnum.i 开始:

%module num

%{
#include <vector>
#include <stdexcept>

std::vector<double> testVec() {
  return std::vector<double>(10,1.0);
}

std::vector<std::vector<double> > testMat() {
  return std::vector<std::vector<double> >(10, testVec());
}
%}

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("num");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

我们为生成的 num_wrap.cxx 提供了 #include以及用于测试的函数的两个实现(它们可以在一个单独的文件中,我只是出于懒惰/方便将它们放在这里)。

我喜欢在 Java SWIG 接口中使用 %pragma(java) jniclasscode= 来实现共享对象/DLL 为接口用户透明地加载。

接口文件中的下一个部分是我们想要包装的 std::vector 部分。我没有使用 std_vector.i 因为我们需要进行一些更改:

namespace std {

    template<class T> class vector {
      public:
        typedef size_t size_type;
        typedef T value_type;
        typedef const value_type& const_reference;
        %rename(size_impl) size;
        vector();
        vector(size_type n);
        size_type size() const;
        size_type capacity() const;
        void reserve(size_type n);
        %rename(isEmpty) empty;
        bool empty() const;
        void clear();
        void push_back(const value_type& x);
        %extend {
            const_reference get_impl(int i) throw (std::out_of_range) {
                // at will throw if needed, swig will handle
                return self->at(i);
            }
            void set_impl(int i, const value_type& val) throw (std::out_of_range) {
                // at can throw
                self->at(i) = val;
            }
        }
    };
}

这里的主要更改是 %rename(size_impl) size;,它告诉 SWIG 公开 <将 std::vector 中的 code>size() 改为 size_impl 。我们需要这样做,因为 Java 期望 size 返回一个 int,而 std::vector 版本返回一个 size_type 很可能不是 int

我们告诉它我们想要实现什么基类和接口,并编写一些额外的 Java 代码来强制类型不兼容的函数之间的事物:

%typemap(javabase) std::vector<double> "java.util.AbstractList<Double>"
%typemap(javainterface) std::vector<double> "java.util.RandomAccess"
%typemap(javacode) std::vector<double> %{
  public Double get(int idx) {
    return get_impl(idx);
  }
  public int size() {
    return (int)size_impl();
  }
  public Double set(int idx, Double d) {
    Double old = get_impl(idx);
    set_impl(idx, d.doubleValue());
    return old;
  }

%}

%typemap(javabase) std::vector<std::vector<double> > "java.util.AbstractList<Vector>"
%typemap(javainterface) std::vector<std::vector<double> > "java.util.RandomAccess"
%typemap(javacode) std::vector<std::vector<double> > %{
  public Vector get(int idx) {
    return get_impl(idx);
  }
  public int size() {
    return (int)size_impl();
  }
  public Vector set(int idx, Vector v) {
    Vector old = get_impl(idx);
    set_impl(idx, v);
    return old;
  }

%}

接下来在接口文件中, Double> 为 std::vectorjava.util.AbstractListstd::向量 >Vector 是我们在接口的 Java 端调用的 std::vector)。

我们还在 Java 端提供了 getset 的实现,可以处理 doubleDouble 的转换以及又回来了。

最后在接口中我们添加:

namespace std {
  %template(Vector) std::vector<double>;
  %template(Matrix) std::vector<vector<double> >;
}

std::vector<double> testVec();
std::vector<std::vector<double> > testMat();

这告诉 SWIG 将 std::vector (具有特定类型)引用为 Vector ,对于 std 也类似: :向量<向量<双> > 作为矩阵。我们还告诉 SWIG 公开我们的两个测试函数。

接下来,test.java,一个简单的 Java 中的 main,用于稍微练习一下我们的代码:

import java.util.AbstractList;

public class test {
  public static void main(String[] argv) {
    Vector v = num.testVec();
    AbstractList<Double> l = v;
    for (Double d: l) {
      System.out.println(d);
    }
    Matrix m = num.testMat();
    m.get(5).set(5, new Double(5.0));
    for (Vector col: m) {
      for (Double d: col) {
        System.out.print(d + " ");
      }
      System.out.println();
    }
  }
}

要构建并运行它,我们需要这样做:

swig -java -c++ num.i
g++ -Wall -Wextra num_wrap.cxx -shared -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux/ -o libnum.so

javac test.java && LD_LIBRARY_PATH=. java test

我使用 g++ 版本 4.4 和 SWIG 对其进行了测试Linux/x86 上的 1.3.40。

num.i 的完整版本可以在此处找到a>,但始终可以通过将每个部分粘贴到一个文件中来根据此答案进行重建。

我没有从 AbstractList 实现的东西:

  1. add() - 可以通过 push_back() 实现,std_vector.i 甚至尝试实现一些东西默认情况下兼容,但它不适用于 Doubledouble 问题或匹配 AbstractList 中指定的返回类型(不要忘记增加 modCount)
  2. remove() - 就时间复杂度而言,对于 std::vector 来说不是很好,但也不是不可能实现(与 modCount 类似>)
  3. 一个构造函数,它接受另一个构造函数推荐使用Collection,但这里没有实现。可以在 set()get() 的同一位置实现,但需要 $javaclassname 来正确命名生成的构造函数。
  4. 您可能需要使用类似 this 的内容来检查 size() 中的 >size_type->int 转换是合理的。

The appropriate base type for wrapping std::vector in Java is java.util.AbstractList. Using java.util.Vector as a base would be odd because you'd end up with two sets of storage, one in the std::vector, and one in the java.util.Vector.

The reason SWIG doesn't do this for you though is because you can't have AbstractList<double> in Java, it has to be AbstractList<Double> (Double inherits from Object whereas double is a primitive type).

Having said all that I've put together a small example that wraps std::vector<double> and std::vector<std::vector<double> > nicely in Java. It's not complete, but it supports the "for each" style of iteration in Java and set()/get() on elements. It should be sufficient to show how to implement other things as/when you want them.

I'll talk through the interface file in sections as we go, but basically it'll all be sequential and complete.

Starting with num.i which defines our module num:

%module num

%{
#include <vector>
#include <stdexcept>

std::vector<double> testVec() {
  return std::vector<double>(10,1.0);
}

std::vector<std::vector<double> > testMat() {
  return std::vector<std::vector<double> >(10, testVec());
}
%}

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("num");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

We have #includes for the generated num_wrap.cxx and two implementations of functions for testing (they could be in a separate file I just put them here out of laziness/convenience).

There's also a trick there with the %pragma(java) jniclasscode= that I like to use in Java SWIG interfaces to cause the shared object/DLL to be loaded transparently for the user of the interface.

Next up in the interface file is the parts of std::vector we want to wrap. I'm not using std_vector.i because we need to make a few changes:

namespace std {

    template<class T> class vector {
      public:
        typedef size_t size_type;
        typedef T value_type;
        typedef const value_type& const_reference;
        %rename(size_impl) size;
        vector();
        vector(size_type n);
        size_type size() const;
        size_type capacity() const;
        void reserve(size_type n);
        %rename(isEmpty) empty;
        bool empty() const;
        void clear();
        void push_back(const value_type& x);
        %extend {
            const_reference get_impl(int i) throw (std::out_of_range) {
                // at will throw if needed, swig will handle
                return self->at(i);
            }
            void set_impl(int i, const value_type& val) throw (std::out_of_range) {
                // at can throw
                self->at(i) = val;
            }
        }
    };
}

The main change here is %rename(size_impl) size;, which tells SWIG to expose size() from std::vector as size_impl instead. We need to do this because Java expects size to return an int where as the std::vector version returns a size_type which more than likely won't be int.

Next up in the interface file we tell it what base class and interfaces we want to implement as well as writing some extra Java code to coerce things between functions with incompatible types:

%typemap(javabase) std::vector<double> "java.util.AbstractList<Double>"
%typemap(javainterface) std::vector<double> "java.util.RandomAccess"
%typemap(javacode) std::vector<double> %{
  public Double get(int idx) {
    return get_impl(idx);
  }
  public int size() {
    return (int)size_impl();
  }
  public Double set(int idx, Double d) {
    Double old = get_impl(idx);
    set_impl(idx, d.doubleValue());
    return old;
  }

%}

%typemap(javabase) std::vector<std::vector<double> > "java.util.AbstractList<Vector>"
%typemap(javainterface) std::vector<std::vector<double> > "java.util.RandomAccess"
%typemap(javacode) std::vector<std::vector<double> > %{
  public Vector get(int idx) {
    return get_impl(idx);
  }
  public int size() {
    return (int)size_impl();
  }
  public Vector set(int idx, Vector v) {
    Vector old = get_impl(idx);
    set_impl(idx, v);
    return old;
  }

%}

This sets a base class of java.util.AbstractList<Double> for std::vector<double> and java.util.AbstractList<Vector> for std::vector<std::vector<double> > (Vector is what we will be calling std::vector<double> on the Java side of the interface).

We also supply an implementation of get and set on the Java side which can handle the double to Double conversion and back again.

Lastly in the interface we add:

namespace std {
  %template(Vector) std::vector<double>;
  %template(Matrix) std::vector<vector<double> >;
}

std::vector<double> testVec();
std::vector<std::vector<double> > testMat();

This tells SWIG to refer to std::vector<double> (with the specific type) as Vector and similarly for std::vector<vector<double> > as Matrix. We also tell SWIG to expose our two test functions.

Next up, test.java, a simple main in Java to exercise our code a little:

import java.util.AbstractList;

public class test {
  public static void main(String[] argv) {
    Vector v = num.testVec();
    AbstractList<Double> l = v;
    for (Double d: l) {
      System.out.println(d);
    }
    Matrix m = num.testMat();
    m.get(5).set(5, new Double(5.0));
    for (Vector col: m) {
      for (Double d: col) {
        System.out.print(d + " ");
      }
      System.out.println();
    }
  }
}

To build and run this we do:

swig -java -c++ num.i
g++ -Wall -Wextra num_wrap.cxx -shared -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux/ -o libnum.so

javac test.java && LD_LIBRARY_PATH=. java test

I tested this with g++ version 4.4 and SWIG 1.3.40 on Linux/x86.

The complete version of num.i can be found here, but can always be reconstructed from this answer by pasting each of the part together into one file.

Things I've not implemented from AbstractList:

  1. add() - can be implemented via push_back(), std_vector.i even tries to implement something compatible by default, but it doesn't work with the Double vs double problem or match the return type specified in AbstractList (Don't forget to increment modCount)
  2. remove() - not great for std::vector in terms of time complexity, but not impossible to implement either (likewise with modCount)
  3. A constructor which takes another Collection is recommended, but not implemented here. Can be implemented at the same place set() and get() are, but will need $javaclassname to name the generated constructor correctly.
  4. You might want to use something like this to check that the size_type->int conversion in size() is sane.
素罗衫 2024-12-23 11:02:26

我是在这个问题上提供赏金的人,因为我也遇到了同样的问题。我有点尴尬地告诉大家,我终于找到了真正的解决方案——它就在 SWIG 手册中!修复方法是在编译生成的代码时使用 g++-fno-strict-aliasing 标志 - 就这么简单。我不愿承认,我花了很多时间谷歌搜索才最终发现这一点。

问题在于,最新版本的 g++ 做了一些激进的优化,这些优化对指针别名做出了一些假设,而这些假设不适用于 SWIG 为 std_vector 生成的代码(以及其他情况下的代码)。 ) g++ 4.1 不会这样做,但 4.4.5 肯定会这样做。这些假设是完全有效的,并且是当前 ISO 标准所允许的,尽管我不确定它们的知名度如何。基本上,不同类型的两个指针(有一些例外)永远不能指向同一个地址。 SWIG 生成的用于在对象指针和 jlong​​ 之间进行转换的代码违反了此规则。

I'm the person who offered the bounty on this question because I had the same issue. I'm a bit embarrassed to report that I've finally found the real solution -- and it's in the SWIG manual! The fix is to use the -fno-strict-aliasing flag for g++ when compiling the generated code -- simple as that. I hate to admit that it took a lot of Googling to finally found this out.

The problem is that recent versions of g++ do some aggressive optimizations that make assumptions about pointer aliasing that don't hold for the code SWIG generates for std_vector (and in other cases.) g++ 4.1 doesn't do this, but 4.4.5 definitely does. The assumptions are perfectly valid and allowed by the current ISO standard, although I'm not sure how well known they are. Basically, it's that two pointers of different types (with a few exceptions) can never point to the same address. The code that SWIG generates to convert between pointer-to-object and jlong falls afoul of this rule.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文