使用SWIG生成Java接口

问题描述:

我正在使用SWIG将C++库(关于Json(de)序列化)的Java封装器用于Android上。我在C++定义的一个抽象类,表示序列化的对象可以是(6):使用SWIG生成Java接口

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

现在,我试图从这个类生成的Java接口。这里是我痛饮接口:

%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接口?

+0

为什么? Java已经有JSON API,只是使用其中的一种。 –

+0

@ChrisDennett:我已经将这个库用于C++中的其他用法。在不久的将来我还有其他的图书馆,所以我也会遇到同样的问题。 –

+0

我不明白 - 你想在这里产生什么? SWIG生成与您显示的声明和定义相匹配的代理,这就是它在这里完成的。这是[这个问题](http://www.swig.org/Doc1.3/Java.html#adding_downcasts)的情况吗?我可以给你一个具体的例子,如果你让你想要更清楚一点。 – Flexo

您可以使用“Directors”实现SWIG + Java所需的内容,但它不像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侧面直观地工作。整个模块

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

在这里,我们启用了导演,然后要求他们用于class Interface明确:我们可以通过定义以下SWIG接口做到这一点。除此之外,我最喜欢的“自动加载共享对象”代码没有什么特别值得注意的。

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

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

然后我们就可以运行这个,看看它的工作如期望的那样:

AJW @莴苣:〜/代码/刮/痛饮/ javaintf> Java中,我们可以用下面的Java类测试这个运行
来自Java的你好!

如果你喜欢它既不abstract也不是interface你可以停止阅读这里,导演你需要的一切。

为什么SWIG生成class而不是interface

然而,SWIG使得看起来像抽象类的东西变成了具体的东西。这意味着在Java方面我们可以合法编写new Interface();,这是没有意义的。 SWIG为什么这样做? class甚至不是abstract,更不用说interface(见第4点here),这在Java方面会感觉更自然。答案是双重的:

  1. SWIG提供调用delete的机制,在Java端操纵cPtr等。这根本不能在interface中完成。
  2. 考虑我们裹以下功能的情况下:

    Interface *find_interface(); 
    

    这里痛饮知道没有更多的返回类型比它的Interface类型。在一个理想的世界里,它会知道派生类型是什么,但是从函数签名本身来看,没有办法让它弄清楚。这意味着在生成的Java 的某个地方必须要调用new Interface,如果Interface在Java端是抽象的,这将不可能/合法。

可能的解决方法

如果你希望以表达对在Java中多重继承,这将是相当限制类型层次提供这作为一个接口。有不过解决方法:

  1. 手动编写接口作为一个适当的Java接口:

    public interface Interface { 
        public String foo(); 
    } 
    
  2. 修改痛饮接口文件:

    1. 重命名C++类InterfaceNativeInterface在Java方面。 (我们应该让它只对所涉及的软件包可见,我们的包装代码会自己包装以避免人们做“疯狂”的事情。使用NativeInterface作为Java端类型。我们需要typemaps这个NativeInterface在功能参数映射到我们手动添加的Interface Java接口。
    2. 马克NativeInterface作为执行Interface使Java端行为的自然和可信的Java用户
    3. 我们需要提供一些额外的代码,这些代码可以作为代理实现Java Interface而不是NativeInterface
    4. 我们传递到C什么++必须始终是NativeInterface不过,并非所有的Interface旨意是一个虽然(尽管所有NativeInterfaces会),所以我们提供了一些胶水,使Interface小号表现为NativeInterfaces,和类型映射到应用该胶水。(对于pgcppname的讨论,请参阅this document

    这导致模块文件,现在看起来像:

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

现在我们可以包装就像一个功能:

// %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 @莴苣:〜/代码/刮/痛饮/ javaintf> Java运行
从Java 您好!
你好从C++

而且我们已经在Java中包裹从C++抽象类作为接口正是因为Java程序员所期望的!

+0

您可以使用类似[this]的方法自动编写'NativeIntefaceProxy'类(http://*.com/questions/3291637/alternatives-to-java-lang-reflect-proxy-for-creating-proxies-抽象类) – Flexo

+3

正是我需要的,并且很好地解释了!非常感谢。 –

+2

我认为SWIG 3.1很可能会在发布时大幅改变这个答案:https://github.com/swig/swig/blob/master/Lib/java/swiginterface.i – Flexo