使用SWIG将C++纯虚类包装为Java Director时的构造器问题
我有一个具备Sender和Listener功能的C库,其中Listener是供客户端扩展的纯虚类,Sender采用std::shared_ptr<MyListener>接收实例以避免悬垂指针。我使用SWIG 4.3的shared_ptr特性包装智能指针类型,director特性支持在Java侧扩展MyListener类,转换成功后在Java程序中编译报错:MyListener无默认构造器,仅存在带long cPtr、boolean cMemoryOwn参数的显式构造器。即使在C的MyListener中显式声明默认构造器,生成的Java类仍无该构造器。现咨询:
- 如何避免生成该显式构造器并保留shared_ptr参数传递?
- 若无法避免,构造器的两个参数应传入什么值?
- 如何正确在Java中扩展该虚拟类?
相关代码
C++定义
class MyListener { public: inline virtual ~MyListener() = default; virtual void notify(const std::string &event) = 0; virtual void disposed(bool expected) = 0; }; class MySender { public: void addListener(std::shared_ptr<MyListener> listener); void removeListener(std::shared_ptr<MyListener> listener); };
SWIG接口文件
%include <std_shared_ptr.i> %module(directors="1") MyWrapper %{ #include "MyClasses.h" %} %shared_ptr(MyListener); %feature("director") MyListener; %include "MyClasses.h"
预期Java用法
public class Main { static { System.loadLibrary("MyWrapper"); } public class DerivedListener extends MyListener{ @Override public void notify(String event) { // do something } @Override public void disposed(boolean expected) { // do something } } public static void main(String[] args) { MySender sender = new MySender(); DerivedListener listener = new DerivedListener(); sender.addListener(listener); sender.removeListener(listener); return; } }
生成的Java类MyListener构造器
protected MyListener(long cPtr, boolean cMemoryOwn) { swigCMemOwn = cMemoryOwn; swigCPtr = cPtr; }
1. 能否避免生成该显式构造器?
无法直接避免生成这个显式构造器。这是SWIG生成JNI绑定的必要结构:director类需要通过底层C对象的指针完成Java与C的方法转发,即使在C中为MyListener添加默认构造器,SWIG也不会为Java类生成默认构造器——因为C纯虚类本身无法直接实例化,SWIG会强制Java类通过JNI绑定的方式创建对应实例。
2. 构造器参数的含义与手动调用风险
绝对不要手动调用这个构造器,它是SWIG内部用于绑定C++对象的接口。如果强行传入参数:
cPtr是底层C++MyListener实例的内存地址cMemoryOwn表示Java侧是否拥有该对象的内存管理权
手动传入参数极易导致内存泄漏、野指针等严重问题,完全不推荐这么做。
3. 正确在Java中扩展虚拟类的方法
利用SWIG的director机制自动处理底层对象创建,只需调整SWIG接口和Java代码:
步骤1:修改SWIG接口,添加默认构造器包装
在SWIG接口文件中,为MyListener添加%extend块,让SWIG生成可供Java调用的默认构造器:
%include <std_shared_ptr.i> %module(directors="1") MyWrapper %{ #include "MyClasses.h" %} %shared_ptr(MyListener); %feature("director") MyListener; // 添加这部分代码,生成Java侧的默认构造器 %extend MyListener { MyListener() { // SWIG会自动生成director代理类,无需手动实例化纯虚类 return new $self(); } } %include "MyClasses.h"
步骤2:调整Java代码
将内部类改为静态内部类(或独立类),确保可以在静态main方法中实例化,并显式调用父类构造器:
public class Main { static { System.loadLibrary("MyWrapper"); } // 改为静态内部类,或提取为独立类 public static class DerivedListener extends MyListener { public DerivedListener() { // 调用SWIG生成的默认构造器,自动创建C++侧的director代理对象 super(); } @Override public void notify(String event) { System.out.println("Received event: " + event); } @Override public void disposed(boolean expected) { System.out.println("Disposed, expected: " + expected); } } public static void main(String[] args) { MySender sender = new MySender(); DerivedListener listener = new DerivedListener(); sender.addListener(listener); sender.removeListener(listener); } }
原理说明
SWIG的director机制会在Java类继承MyListener时,自动创建一个C代理类,这个代理类会将C侧的回调转发给Java实现。%shared_ptr(MyListener)会让SWIG自动完成Java对象到std::shared_ptr<MyListener>的转换,无需手动处理智能指针的传递逻辑。
内容的提问来源于stack exchange,提问作者Evan Altair

