使用 SWIG 生成 Java 接口

发布于 2024-12-16 02:13:36 字数 1574 浏览 0 评论 0原文

我正在使用 SWIG 制作 C++ 库的 Java 包装器(关于 Json(反)序列化)以在 Android 上使用它。我在 C++ 中定义了一个抽象类,表示一个可以序列化(反序列化)的对象:

class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0; 
    virtual void deserialize(Value &root) = 0; 
};

现在,我尝试从该类生成一个 Java 接口。这是我的 SWIG 接口:

%module JsonSerializable
%{
#include "JsonSerializable.hpp"
%}

%import "JsonValue.i"

class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0; 
    virtual void deserialize(Value &root) = 0;
};

但是生成的 Java 代码(显然,因为我无法找到如何告诉 SWIG 这是一个接口)一个简单的类,具有两个方法和一个默认构造函数/析构函数:

public class IJsonSerializable {
  private long swigCPtr;
  protected boolean swigCMemOwn;

  public IJsonSerializable(long cPtr, boolean cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
  }  

  public static long getCPtr(IJsonSerializable obj) {
    return (obj == null) ? 0 : obj.swigCPtr;
  }  

  protected void finalize() {
    delete();
  }  

  public synchronized void delete() {
    if (swigCPtr != 0) {
      if (swigCMemOwn) {
        swigCMemOwn = false;
        JsonSerializableJNI.delete_IJsonSerializable(swigCPtr);
      }  
      swigCPtr = 0; 
    }  
  }  

  public void serialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_serialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

  public void deserialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_deserialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

}

如何生成一个与 SWIG 的有效接口?

I'm using SWIG to make a Java wrapper of a C++ library (about Json (de)serialization) to use it on Android. I defined an abstract class in C++, representing an object which can be (de)serialized :

class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0; 
    virtual void deserialize(Value &root) = 0; 
};

Now, I'm trying to generate from this class a Java interface. Here's my SWIG interface:

%module JsonSerializable
%{
#include "JsonSerializable.hpp"
%}

%import "JsonValue.i"

class IJsonSerializable {
public:
    virtual void serialize(Value &root) = 0; 
    virtual void deserialize(Value &root) = 0;
};

But the generated Java code is (obviously, as I was not able to find out how to tell SWIG that's an interface) a simple class, with the two methods and a default constructor/destructor:

public class IJsonSerializable {
  private long swigCPtr;
  protected boolean swigCMemOwn;

  public IJsonSerializable(long cPtr, boolean cMemoryOwn) {
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
  }  

  public static long getCPtr(IJsonSerializable obj) {
    return (obj == null) ? 0 : obj.swigCPtr;
  }  

  protected void finalize() {
    delete();
  }  

  public synchronized void delete() {
    if (swigCPtr != 0) {
      if (swigCMemOwn) {
        swigCMemOwn = false;
        JsonSerializableJNI.delete_IJsonSerializable(swigCPtr);
      }  
      swigCPtr = 0; 
    }  
  }  

  public void serialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_serialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

  public void deserialize(Value root) {
    JsonSerializableJNI.IJsonSerializable_deserialize(swigCPtr, this, Value.getCPtr(root), root);
  }  

}

How can I generate a valid interface with SWIG ?

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

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

发布评论

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

评论(1

又怨 2024-12-23 02:13:36

您可以使用“Directors”,但是它并不像您希望的那样直接从 C++ 抽象类映射到 Java。因此,我的答案分为三个部分 - 首先是在 Java 中实现 C++ 纯虚函数的简单示例,其次是对输出为何如此的解释,第三是“解决方法”。

在 Java 中实现 C++ 接口

给定一个头文件 (module.hh):

#include <string>
#include <iosfwd>

class Interface {
public:
  virtual std::string foo() const = 0;
  virtual ~Interface() {}
};

inline void bar(const Interface& intf) {
  std::cout << intf.foo() << std::endl;
}

我们希望对其进行包装并使其从 Java 端直观地工作。我们可以通过定义以下 SWIG 接口来做到这一点:

%module(directors="1") test

%{
#include <iostream>
#include "module.hh"
%}

%feature("director") Interface;
%include "std_string.i"

%include "module.hh"

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

这里我们为整个模块启用了控制器,然后请求它们专门用于类接口。除了这个和我最喜欢的“自动加载共享对象”代码之外,没有什么特别值得注意的。我们可以使用以下 Java 类对此进行测试:

public class Run extends Interface {
  public static void main(String[] argv) {
    test.bar(new Run());       
  }

  public String foo() {
    return "Hello from Java!";
  }
}

然后我们可以运行它并查看它是否按预期工作:

ajw@rapunzel:~/code/scratch/swig/javaintf> java运行
来自 Java,您好!

如果您对它既不是抽象也不是界面感到满意,您可以停止阅读这里,导演会做您需要的一切。

为什么 SWIG 生成而不是接口

然而,SWIG 将看似抽象的类变成了具体的类。这意味着在 Java 方面我们可以合法地编写 new Interface();,这是没有意义的。 SWIG 为什么这样做? 甚至不是抽象,更不用说接口了(参见第4点此处),这在 Java 方面感觉更自然。答案是双重的:

  1. SWIG 提供了在 Java 端调用删除、操作 cPtr 等的机制。这根本无法在界面中完成。
  2. 考虑我们包装以下函数的情况:

    接口 *find_interface();
    

    这里,SWIG 除了了解其 Interface 类型之外,对返回类型一无所知。在理想的情况下,它会知道派生类型是什么,但仅从函数签名来看,它无法弄清楚这一点。这意味着在生成的 Java某处中,必须调用 new Interface,如果 Interface 则这是不可能/合法的> 在 Java 方面是抽象的。

可能的解决方法

如果您希望将其作为接口提供,以便在 Java 中表达具有多重继承的类型层次结构,那么这将是相当有限的。不过,有一个解决方法:

  1. 手动将接口编写为正确的 Java 接口:

    公共接口接口{
        公共字符串foo();
    }
    
  2. 修改 SWIG 接口文件:

    1. 在 Java 端将 C++ 类 Interface 重命名为 NativeInterface。 (我们也应该让它只对有问题的包可见,我们包装的代码位于它自己的包中,以避免人们做“疯狂”的事情。
    2. 在 C++ 代码中存在 Interface 的地方,SWIG 现在将使用 NativeInterface 作为 Java 端的类型。我们需要类型映射将函数参数中的 NativeInterface 映射到我们手动添加的 Interface Java 接口。
    3. NativeInterface 标记为实现 Interface,以使 Java 端行为对 Java 用户来说自然且可信。
    4. 我们需要提供一些额外的代码,这些代码可以充当实现 Java Interface 的代理,而无需成为 NativeInterface
    5. 我们传递给 C++ 的内容必须始终是一个 NativeInterface,但并非所有 Interface 都是一个(尽管所有 NativeInterface 都会) ,因此我们提供了一些粘合来使 Interface 表现得像 NativeInterfaces ,以及一个类型映射来应用该粘合。 (有关 pgcppname本文档代码>)

    这会产生一个现在看起来像这样的模块文件:

    %module(directors="1") 测试
    
    %{
    #include ;
    #include“模块.hh”
    %}
    
    %feature("导演") 界面;
    %包括“std_string.i”
    
    //(2.1)
    %重命名(NativeInterface)接口; 
    
    //(2.2)
    %typemap(jstype) const 接口& “界面”;
    
    //(2.3)
    %typemap(javainterfaces) 接口“接口”
    
    //(2.5)
    %typemap(javain,pgcppname="n",
             pre="NativeInterface n = makeNative($javainput);")
            const 接口& “NativeInterface.getCPtr(n)”
    
    %包括“模块.hh”
    
    %pragma(java) 模块代码=%{
      //(2.4)
      私有静态类 NativeInterfaceProxy 扩展 NativeInterface {
        私有接口委托;
        公共NativeInterfaceProxy(接口i){
          委托=我;
        }
    
        公共字符串 foo() {
          返回 delegate.foo();
        }
      }
    
      //(2.5)
      私有静态NativeInterface makeNative(接口i){
        if (i NativeInterface 实例) {
          // 如果它已经*是*一个 NativeInterface 就不用再费心包装它了
          返回(NativeInterface)i;
        }
        返回新的 NativeInterfaceProxy(i);
      }
    %}
    

现在我们可以包装一个函数,如:

// %inline = wrap and define at the same time
%inline %{
  const Interface& find_interface(const std::string& key) {
    static class TestImpl : public Interface {
      virtual std::string foo() const {
        return "Hello from C++";
      }
    } inst;
    return inst;
  }
%}

并使用它,如下所示:

import java.util.ArrayList;

public class Run implements Interface {
  public static void main(String[] argv) {
    ArrayList<Interface> things = new ArrayList<Interface>();
    // Implements the interface directly
    things.add(new Run()); 
    // NativeInterface implements interface also
    things.add(test.find_interface("My lookup key")); 

    // Will get wrapped in the proxy 
    test.bar(things.get(0));

    // Won't get wrapped because of the instanceOf test
    test.bar(things.get(1));
  }

  public String foo() {
    return "Hello from Java!";
  }
}

现在,它按您希望的方式运行:

ajw@rapunzel:~/code/scratch/swig/javaintf> java运行
来自 Java,您好!
来自 C++ 的你好

我们已经将 C++ 的抽象类包装为 Java 中的接口,正如 Java 程序员所期望的那样!

You can achieve what you're looking for with SWIG+Java using "Directors", however it's not quite as straightforward mapping from the C++ abstract classes onto Java as you might hope. My answer therefore is split into three parts - firstly the simple example of implementing a C++ pure virtual function in Java, secondly an explanation of why the output is like that and thirdly a "work-around".

Implementing a C++ interface in Java

Given a header file (module.hh):

#include <string>
#include <iosfwd>

class Interface {
public:
  virtual std::string foo() const = 0;
  virtual ~Interface() {}
};

inline void bar(const Interface& intf) {
  std::cout << intf.foo() << std::endl;
}

We'd like to wrap this and make it work intuitively from the Java side. We can do this by defining the following SWIG interface:

%module(directors="1") test

%{
#include <iostream>
#include "module.hh"
%}

%feature("director") Interface;
%include "std_string.i"

%include "module.hh"

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

Here we've enabled directors for the whole module, and then requested they be used for class Interface specifically. Other than that and my favourite "load the shared object automatically" code there's nothing particularly noteworthy. We can test this with the following Java class:

public class Run extends Interface {
  public static void main(String[] argv) {
    test.bar(new Run());       
  }

  public String foo() {
    return "Hello from Java!";
  }
}

We can then run this and see it's working as expected:

ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!

If you're happy with it being neither abstract nor an interface you can stop reading here, directors do everything you need.

Why does SWIG generate a class instead of an interface?

SWIG has however made what looked like an abstract class into a concrete one. That means on the Java side we could legally write new Interface();, which makes no sense. Why does SWIG do this? The class isn't even abstract, let alone an interface (See point 4 here), which would feel more natural on the Java side. The answer is twofold:

  1. SWIG supplies mechanics for calling delete, manipulating the cPtr etc. on the Java side. That couldn't be done in an interface at all.
  2. Consider the case where we wrapped the following function:

    Interface *find_interface();
    

    Here SWIG knows nothing more about the return type than that it's of type Interface. In an ideal world it would know what the derived type is, but from the function signature alone there's no way for it to figure this out. This means that in the generated Java somewhere there's going to have to be a call to new Interface, which wouldn't be possible/legal if Interface were abstract on the Java side.

Possible workaround

If you were hoping to provide this as an interface in order to express a type hierarchy with multiple inheritance in Java this would be quite limiting. There's a workaround however:

  1. Manually write the interface as a proper Java interface:

    public interface Interface {
        public String foo();
    }
    
  2. Modify the SWIG interface file:

    1. Rename the C++ class Interface to be NativeInterface on the Java side. (We ought to make it visible only to the package in question too, with our wrapped code living in a package of its own to avoid people doing "crazy" things.
    2. Everywhere we have an Interface in C++ code SWIG will now be using NativeInterface as the type on the Java side. We need typemaps to map this NativeInterface in function parameters onto the Interface Java interface we added manually.
    3. Mark NativeInterface as implementing Interface to make the Java side behaviour natural and believable to a Java user.
    4. We need to supply a little bit of extra code that can act as a proxy for things which implement the Java Interface without being a NativeInterface too.
    5. What we pass to C++ must always be a NativeInterface still, not all Interfaces will be one though (although all NativeInterfaces will), so we provide some glue to make Interfaces behave as NativeInterfaces, and a typemap to apply that glue. (See this document for a discussion of the pgcppname)

    This results in a module file that now looks like:

    %module(directors="1") test
    
    %{
    #include <iostream>
    #include "module.hh"
    %}
    
    %feature("director") Interface;
    %include "std_string.i"
    
    // (2.1)
    %rename(NativeInterface) Interface; 
    
    // (2.2)
    %typemap(jstype) const Interface& "Interface";
    
    // (2.3)
    %typemap(javainterfaces) Interface "Interface"
    
    // (2.5)
    %typemap(javain,pgcppname="n",
             pre="    NativeInterface n = makeNative($javainput);")
            const Interface&  "NativeInterface.getCPtr(n)"
    
    %include "module.hh"
    
    %pragma(java) modulecode=%{
      // (2.4)
      private static class NativeInterfaceProxy extends NativeInterface {
        private Interface delegate;
        public NativeInterfaceProxy(Interface i) {
          delegate = i;
        }
    
        public String foo() {
          return delegate.foo();
        }
      }
    
      // (2.5)
      private static NativeInterface makeNative(Interface i) {
        if (i instanceof NativeInterface) {
          // If it already *is* a NativeInterface don't bother wrapping it again
          return (NativeInterface)i;
        }
        return new NativeInterfaceProxy(i);
      }
    %}
    

Now we can wrap a function like:

// %inline = wrap and define at the same time
%inline %{
  const Interface& find_interface(const std::string& key) {
    static class TestImpl : public Interface {
      virtual std::string foo() const {
        return "Hello from C++";
      }
    } inst;
    return inst;
  }
%}

and use it like:

import java.util.ArrayList;

public class Run implements Interface {
  public static void main(String[] argv) {
    ArrayList<Interface> things = new ArrayList<Interface>();
    // Implements the interface directly
    things.add(new Run()); 
    // NativeInterface implements interface also
    things.add(test.find_interface("My lookup key")); 

    // Will get wrapped in the proxy 
    test.bar(things.get(0));

    // Won't get wrapped because of the instanceOf test
    test.bar(things.get(1));
  }

  public String foo() {
    return "Hello from Java!";
  }
}

This now runs as you'd hope:

ajw@rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!
Hello from C++

And we've wrapped an abstract class from C++ as an interface in Java exactly as a Java programmer would expect!

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