如何使用 SWIG 包裹 std::function 个对象?
How to use SWIG to wrap std::function objects?
我已经看到很多类似的问题,但还没有找到解决我的特定问题的方法。我正在尝试对一些使用 std::function 的 C++11 代码进行 SWIGify,以便我可以在我的 Java 应用程序中使用它。
我遇到过这样的共享指针:
virtual std::shared_ptr<some::ns::TheThing> getTheThing(unsigned short thingID);
并使用 shared_ptr 指令成功处理它们,如下所示:
%shared_ptr(some::ns::TheThing);
我遇到过这样的共享指针向量:
virtual std::vector<std::shared_ptr<some::ns::TheThing>> getAllTheThings() const = 0;
并使用如下模板成功处理了它们:
%template(ThingVector) std::vector<std::shared_ptr<some::ns::TheThing>>;
现在我有这样的方法:
void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);
我无法让 SWIG 正确包装它。我已经尝试使用 %callback、directors、%template 和 %inline 功能代码,因为我已经看到了所有这些东西的例子,但还没有得到任何看起来接近工作的东西。如果有帮助(清理和减少),这里有一些关于函数调用的更多上下文:
thing_callback.h
#include <functional>
namespace some {
namespace ns {
/**
* Hold some callbacks.
*/
class ThingCallbacks {
public:
/**
* Registers a callback
* @param func The callback function
*/
void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);
};
}
}
更新
根据下面 Flexo 的出色回答,我离解决方案更近了。我能够让下面的示例完全按照宣传的方式工作。我尝试将它合并到我的实际代码中,但 运行 变成了问题。为了扩展我之前的简化示例,这里是我对 TheThing 的定义:
test_thing.h
#ifndef THE_THING_H
#define THE_THING_H
#include <string>
namespace some {
namespace ns {
class TheThing {
public:
virtual ~TheThing() {};
virtual unsigned long longThing() const = 0;
virtual std::string stringThing() const = 0;
};
}
}
#endif /* THE_THING_H */
这是我的 .i 文件。为了尽可能少地移动部件,我基本上只是从下面答案中提供的代码中取出 int 和 double,并用指向我的对象的共享指针替换它们。
func_thing_test.i
%module(directors="1") Thing
%include "stl.i"
%include "std_function.i"
%include "std_shared_ptr.i"
%shared_ptr(some::ns::TheThing);
%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)(,false)";
%typemap(directorin,descriptor="Lsome.ns.typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{
*($&1_type*)&j = &;
%}
%include "test_thing.h"
%include "thing_callback.h"
%{
#include <memory>
#include "test_thing.h"
#include "thing_callback.h"
%}
%std_function(Functor, void, std::shared_ptr<some::ns::TheThing>);
%{
#include <iostream>
void add_and_print(std::shared_ptr<some::ns::TheThing> thing) {
std::cout << "here\n";
}
%}
%callback("%s_cb");
void add_and_print(std::shared_ptr<some::ns::TheThing>);
%nocallback;
%inline %{
std::function<void(std::shared_ptr<some::ns::TheThing>)> make_functor() {
return [](std::shared_ptr<some::ns::TheThing>){
std::cout << "make functor\n";
};
}
void do_things(std::function<void(std::shared_ptr<some::ns::TheThing>)> in) {
std::cout << "inside do things\n";
}
%}
test_thing.h 是我刚刚在上面贴出的,thing_callback.h 是我在原问题中贴出的代码。这一切都通过 swig、c++ 和 Java 链编译而没有错误,但看起来 swig 在连接 c++ 和 Java 之间的点时遇到了一些麻烦。它创建了这三个 类:
SWIGTYPE_p_f_std__function__f_std__shared_ptr__some__ns__TheThing____void____void
SWIGTYPE_p_f_std__shared_ptr__some__ns__TheThing____void
SWIGTYPE_p_std__functionT_void_fstd__shared_ptrT_some__ns__TheThing_tF_t
不幸的是,简单 Java 主代码中的大多数方法现在都采用或 return 这些对象,这使得它们相当不可用。知道如何解决这个问题吗?谢谢!
更详细一点以确保完整性:我正在使用以下三个脚本来编译和 运行 代码。参数略有不同,但我认为这不重要。在我这边,它被设置为一个 Eclipse maven 项目。这些脚本位于我项目的根目录中,头文件和 swig 文件位于 src/main/resources,java 源文件位于 src/main/java,java 已编译 类 位于在 target/classes。 Eclipse 执行 java 编译。
swigthing.sh
MODULE_NAME=Thing
PACKAGE=some.ns
OUTDIR=./src/main/java/some/ns
I_FILE=./src/main/resources/func_thing_test.i
mvn clean
rm $OUTDIR/*.*
mkdir -p $OUTDIR
swig -java -c++ -module $MODULE_NAME -package $PACKAGE -outdir $OUTDIR $I_FILE
./compileThingSwigTest.sh
compileThingSwigTest.sh
#!/bin/bash
pushd src/main/resources
g++ -c -std=gnu++11 -fpic \
func_thing_test_wrap.cxx \
-I/usr/lib/jvm/java/include \
-I/usr/lib/jvm/java/include/linux
g++ -shared func_thing_test_wrap.o -o libFunc.so
popd
runThingTest.sh
pushd target/classes
java -Xmx512M -Xms512M -Djava.library.path=. some.ns.test.RunThingTest
popd
最后更新
修复了上面的代码以将正确的参数传递给 std_function。现在在问题和答案之间有一个完整的工作示例,说明我所追求的。
虽然 SWIG 本身不提供 std_function.i,但我们可以通过一些工作自己构建一个。我在这里的回答是我的 a previous of mine answer 的更通用版本,针对特定实例查看此问题并针对 Python。我将对其进行多次迭代,为通用 std::function
包装定义一个 %std_function
宏。
我假设您希望通过包装 std::function
实现四件事,这成为我们的主要要求:
- 我们希望能够从 Java 代码中调用
std::function
个对象。
- 包装的
std::function
对象需要像任何其他对象一样传递,包括在任一方向跨越语言边界。
- 应该可以在 Java 中编写
std::function
个对象,这些对象可以传回 C++,而无需修改现有的 std::function
个对象的 C++ 代码(即维护 std::function
跨语言的类型擦除)
- 我们应该能够使用指向函数类型的 C++ 指针在 Java 中构造
std::function
个对象。
我将完成这些工作并展示我们如何实现这一目标。在可能的情况下,我也会让解决方案语言不可知。
出于讨论的目的,我对你问题的 shared_ptr
部分进行了掩饰,它实际上并没有改变什么,因为你已经 shared_ptr
工作了,这实际上足以使用在这种情况下也是如此,它只会使我的示例变得不必要地冗长。
我正在努力的解决方案实际上是根据 SWIG 中现有的 shared_ptr
支持建模的。我整理了一个测试界面来说明如何使用它:
%module test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
%}
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
%}
基本上,要使用它,您需要做的就是包含文件 "std_function.i",然后使用宏 %std_function
,其参数为:
%std_function(Name, Ret, ...)
每次实例化要包装的 std::function
模板时调用一次,其中 Name
是您要在 Java 中调用的类型,Ret 是 return 类型,然后剩余的(可变参数)参数是您函数的输入。所以在我上面的测试界面中,我基本上是想包装 std::function<void(int,double)>
.
编写 "std_function.i" 的第一个版本实际上并不太难。获得基本工作要求 #1 和 #2 所需要做的就是:
%{
#include <functional>
%}
%define %std_function(Name, Ret, ...)
%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();
namespace std {
struct function<Ret(__VA_ARGS__)> {
// Copy constructor
function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);
// Call operator
Ret operator()(__VA_ARGS__) const;
};
}
%enddef
这在生成的包装器代码中包含一次 C++ 头文件,然后设置宏以在接口中使用。 SWIG 支持使用 C99 可变宏参数(得到更好的支持)的 C++11 variadic templates isn't actually very helpful for us in this usage scenario, so the macro I wrote basically re-implements the default template expansion functionality。巧合的是,这意味着我们正在编写的 SWIG 代码将适用于 2.x 甚至某些 1.3.x 版本。 (我用 2.x 测试过)。即使 if/when 你的 SWIG 版本确实有 %template
支持与 std::function
一起工作,保留这个宏对于使其实际可调用的其余胶水仍然有用。
std:function
模板的手动扩展仅限于我们关心的用法:实际的 operator()
和可能派上用场的复制构造函数。
唯一要做的就是将 operator()
重命名为与目标语言匹配的名称,例如Java 将其重命名为一个名为 "call" 的常规函数,或者如果您将 Python 定位为 __call__
或在需要时使用 tp_slots。
这足以让我们的界面工作,为了演示它我写了一点 Java:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
}
}
我编译的是:
swig2.0 -Wall -c++ -java test.i
g++ -Wall -Wextra -std=c++11 test_wrap.cxx -o libtest.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux -shared -fPIC
javac run.java
LD_LIBRARY_PATH=. java run
成功了。
此时要求 #4 很容易从列表中划掉。我们需要做的就是告诉 SWIG std::function
中还有另一个构造函数,它接受兼容的函数指针:
// Conversion constructor from function pointer
function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));
然后我们可以在 SWIG 中将其与 %callback
mechanism 一起使用,我们的测试接口文件变为:
%module test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
void add_and_print(int a, double b) {
std::cout << a+b << "\n";
}
%}
%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
%}
然后我们用来称呼它的 Java 是:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
new Functor(test.add_and_print_cb).call(3,4.5);
}
}
此时我们编译和 运行 同样成功。
(请注意,此时看到一些以名称 "SWIGTYPE_p_f_..." 开头的 Java classes 是正常且可取的 - 这些包含 "pointer to function" 类型由指向函数构造函数和回调常量的指针使用)
要求 #3 是事情开始变得棘手的地方。从本质上讲,我们遇到了与我之前回答的问题相同的问题 on making SWIG generate an interface in Java,除了现在我们希望在更通用的宏中完成它。
事实证明,在这种情况下,因为我们想要生成的界面相当简单,所以我们可以在宏中使用一些技巧让 SWIG 为我们生成它。
为了完成这项工作,我们需要做的主要事情是设置 SWIG 控制器以提供跨语言多态性,并允许用 Java 编写的内容实现 C++ 接口。这是我代码中生成的后缀为"Impl"的class。
为了让 "feel right" 给 Java 开发人员,我们希望 C++ 和 Java 实现的 std::function
对象仍然使用相同的类型。即使 std::function::operator()
是虚拟的,我们仍然不希望 SWIG 主管直接使用该类型,因为按值传递 std::function
很常见,这会导致类型 slicing problems。因此,当 Java 开发人员扩展我们的 std::function
对象并覆盖 call
时,我们需要做一些额外的工作来实现它,以便使用该对象的 C++ 实际上调用 Java 实现考虑到我们不能只使用 directors 来自动处理这个问题。
所以我们所做的看起来有点奇怪。如果你构造一个 Java 对象来实现 std::function
那么有一个特殊的,受保护的构造函数。此构造函数保留 swigCPtr
成员变量,该变量通常指向一个真正的 C++ 对象 0,而是创建一个实现 "Impl" 接口的匿名包装对象,并简单地将所有内容代理回 call
Java 对象的成员。
我们还有另一个应用类型映射,在 Java 中,我们将 std::function
对象传递给 C++ 的任何地方。它的作用是检测我们有哪种情况 - C++ 实现的 std::function
对象,或 Java 对象。在 C++ 的情况下,它没有做任何特别的事情,一切都照常进行。在 Java 情况下,它获取代理对象并要求 C++ 将其转换回另一个单独的 std::function
实例,该实例被替换。
这足以让我们在两种语言中获得我们想要的行为,而没有任何两边感觉奇怪的东西(除了大量机械提升之外 t运行sparently)。
这里要注意的是,自动构造代理对象并非易事。 Java 有 dynamic proxy classes 作为反射 API 的一部分,但这些只实现接口,不扩展抽象 classes。我确实尝试使用的一种可能性是 Java 端的 void call(Object ...args)
,这是一个可变函数参数。虽然合法,但这似乎并没有真正覆盖 super class 中需要的任何情况。
我最终做的是调整 some macros 以我想要的方式迭代可变宏参数。这是一个相当明智的解决方案,因为我们已经出于其他原因决定使用可变参数 C99 宏参数。这种机制在我的解决方案中总共使用了四次,一次在函数声明中,一次在 Java 和 C++ 的委托调用中。 (C++ 保留了完美的转发属性,Java 需要执行类型映射查找,因此它们在每种情况下都是不同的)。
还有一个自定义类型映射来简化一些 Java 代码 - 在 void 函数中编写 return other_void_function();
是不合法的,所以我们需要特殊情况下的 void 函数,如果它不是'
让我们看看现实中的样子。首先是我用于测试的 run.java,它只是对前面的示例稍作修改,添加了 std::function
对象的 Java 实现。
public class run extends Functor {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
new Functor(test.add_and_print_cb).call(3,4.5);
Functor f = new run();
test.do_things(f);
}
@Override
public void call(int a, double b) {
System.out.println("Java: " + a + ", " + b);
}
}
std_function.i 现在有了上面列出的所有更改后显着变大:
%{
#include <functional>
#include <iostream>
#ifndef SWIG_DIRECTORS
#error "Directors must be enabled in your SWIG module for std_function.i to work correctly"
#endif
%}
// These are the things we actually use
#define param(num,type) $typemap(jstype,type) arg ## num
#define unpack(num,type) arg##num
#define lvalref(num,type) type&& arg##num
#define forward(num,type) std::forward<type>(arg##num)
// This is the mechanics
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1), action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1), action(1,a2), action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1), action(1,a2), action(2,a3), action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1), action(1,a2), action(2,a3), action(3,a4), action(4,a5)
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef
%define %std_function(Name, Ret, ...)
%feature("director") Name##Impl;
%typemap(javaclassmodifiers) Name##Impl "abstract class";
%{
struct Name##Impl {
virtual ~Name##Impl() {}
virtual Ret call(__VA_ARGS__) = 0;
};
%}
%javamethodmodifiers Name##Impl::call "abstract protected";
%typemap(javaout) Ret Name##Impl::call ";" // Suppress the body of the abstract method
struct Name##Impl {
virtual ~Name##Impl();
protected:
virtual Ret call(__VA_ARGS__) = 0;
};
%typemap(maybereturn) SWIGTYPE "return ";
%typemap(maybereturn) void "";
%typemap(javain) std::function<Ret(__VA_ARGS__)> "$javaclassname.getCPtr($javaclassname.makeNative($javainput))"
%typemap(javacode) std::function<Ret(__VA_ARGS__)> %{
protected Name() {
wrapper = new Name##Impl(){
public $typemap(jstype, Ret) call(FOR_EACH(param, __VA_ARGS__)) {
$typemap(maybereturn, Ret)Name.this.call(FOR_EACH(unpack, __VA_ARGS__));
}
};
proxy = new $javaclassname(wrapper);
}
static $javaclassname makeNative($javaclassname in) {
if (null == in.wrapper) return in;
return in.proxy;
}
// Bot of these are retained to prevent garbage collection from happenign to early
private Name##Impl wrapper;
private $javaclassname proxy;
%}
%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();
namespace std {
struct function<Ret(__VA_ARGS__)> {
// Copy constructor
function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);
// Call operator
Ret operator()(__VA_ARGS__) const;
// Conversion constructor from function pointer
function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));
%extend {
function<Ret(__VA_ARGS__)>(Name##Impl *in) {
return new std::function<Ret(__VA_ARGS__)>([=](FOR_EACH(lvalref,__VA_ARGS__)){
return in->call(FOR_EACH(forward,__VA_ARGS__));
});
}
}
};
}
%enddef
并且 test.i 稍微扩展以验证 Java -> C++ 传递 std::function
对象并启用控制器:
%module(directors="1") test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
void add_and_print(int a, double b) {
std::cout << a+b << "\n";
}
%}
%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
void do_things(std::function<void(int,double)> in) {
in(-1,666.6);
}
%}
此编译和 运行 与前面的示例一样。值得注意的是,我们已经编写了很多 Java 特定代码——尽管如果您的目标是 Python,该设计也适用于其他语言,但使用 [= 解决其中一些问题要简单得多=227=] 具体功能。
有两点我想改进:
使用 C++14 可变参数 lambda 来避免我用来保持它们与 C++11 兼容的宏预处理器魔法。如果你有 C++ 14,%extend
构造函数变为:
%extend {
function<Ret(__VA_ARGS__)>(Name##Impl *in) {
return new std::function<Ret(__VA_ARGS__)>([=](auto&& ...param){
return in->call(std::forward<decltype(param)>(param)...);
});
}
}
当按照预期将此宏与 std::shared_ptr
一起使用时,宏本身不需要更改。然而,应用的 javadirectorin 和 directorin 类型映射的实现存在问题,这确实阻止了 "just working"。即使使用来自 "trunk" 的 SWIG 构建也是如此。 (combining directors and shared_ptr 上有一个悬而未决的问题)
我们可以解决这个问题,方法是在调用 %shared_ptr
:
后立即在我们模块的主 .i 文件中添加两个额外的类型映射
%shared_ptr(some::ns::TheThing);
%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)(,false)";
%typemap(directorin,descriptor="L$typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{
*($&1_type*)&j = &;
%}
这两个类型映射中的第一个实际上是死代码,因为我们在我们的抽象class中强制"call"方法是抽象的,但是修复这个方法的编译比它更容易压制它。第二个类型图很重要。它与普通的 "out" 类型映射非常相似,因为它创建了一个 jlong
,它实际上只是 C++ 指针的表示,即它准备了一个从 C++ 到 Java 的对象。
请注意,如果您在模块中使用包,则可能需要修改 directorin 类型映射的描述符属性,"L$packagepath/$typemap(...);"
或直接手动编写。
这也应该删除现在生成的虚假 "SWIGTYPE_p_sstd__shared_ptr..." 类型。如果您有 return shared_ptr 对象的虚函数,您也需要为它们编写 directorout 和 javadirectorout 类型映射。这些可以基于正常的 "in" 类型映射。
这足以让我自己使用修改后的 Functor
进行简单测试,至少对于我今天从 t运行k 检出的 SWIG 版本来说是这样。 (我对 2.0.x 的测试失败了,我没有付出太多努力让它工作,因为这是一个已知的正在进行的工作)。
我已经看到很多类似的问题,但还没有找到解决我的特定问题的方法。我正在尝试对一些使用 std::function 的 C++11 代码进行 SWIGify,以便我可以在我的 Java 应用程序中使用它。
我遇到过这样的共享指针:
virtual std::shared_ptr<some::ns::TheThing> getTheThing(unsigned short thingID);
并使用 shared_ptr 指令成功处理它们,如下所示:
%shared_ptr(some::ns::TheThing);
我遇到过这样的共享指针向量:
virtual std::vector<std::shared_ptr<some::ns::TheThing>> getAllTheThings() const = 0;
并使用如下模板成功处理了它们:
%template(ThingVector) std::vector<std::shared_ptr<some::ns::TheThing>>;
现在我有这样的方法:
void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);
我无法让 SWIG 正确包装它。我已经尝试使用 %callback、directors、%template 和 %inline 功能代码,因为我已经看到了所有这些东西的例子,但还没有得到任何看起来接近工作的东西。如果有帮助(清理和减少),这里有一些关于函数调用的更多上下文:
thing_callback.h
#include <functional>
namespace some {
namespace ns {
/**
* Hold some callbacks.
*/
class ThingCallbacks {
public:
/**
* Registers a callback
* @param func The callback function
*/
void registerThingCallback(std::function<void(std::shared_ptr<some::ns::TheThing>) > func);
};
}
}
更新
根据下面 Flexo 的出色回答,我离解决方案更近了。我能够让下面的示例完全按照宣传的方式工作。我尝试将它合并到我的实际代码中,但 运行 变成了问题。为了扩展我之前的简化示例,这里是我对 TheThing 的定义:
test_thing.h
#ifndef THE_THING_H
#define THE_THING_H
#include <string>
namespace some {
namespace ns {
class TheThing {
public:
virtual ~TheThing() {};
virtual unsigned long longThing() const = 0;
virtual std::string stringThing() const = 0;
};
}
}
#endif /* THE_THING_H */
这是我的 .i 文件。为了尽可能少地移动部件,我基本上只是从下面答案中提供的代码中取出 int 和 double,并用指向我的对象的共享指针替换它们。
func_thing_test.i
%module(directors="1") Thing
%include "stl.i"
%include "std_function.i"
%include "std_shared_ptr.i"
%shared_ptr(some::ns::TheThing);
%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)(,false)";
%typemap(directorin,descriptor="Lsome.ns.typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{
*($&1_type*)&j = &;
%}
%include "test_thing.h"
%include "thing_callback.h"
%{
#include <memory>
#include "test_thing.h"
#include "thing_callback.h"
%}
%std_function(Functor, void, std::shared_ptr<some::ns::TheThing>);
%{
#include <iostream>
void add_and_print(std::shared_ptr<some::ns::TheThing> thing) {
std::cout << "here\n";
}
%}
%callback("%s_cb");
void add_and_print(std::shared_ptr<some::ns::TheThing>);
%nocallback;
%inline %{
std::function<void(std::shared_ptr<some::ns::TheThing>)> make_functor() {
return [](std::shared_ptr<some::ns::TheThing>){
std::cout << "make functor\n";
};
}
void do_things(std::function<void(std::shared_ptr<some::ns::TheThing>)> in) {
std::cout << "inside do things\n";
}
%}
test_thing.h 是我刚刚在上面贴出的,thing_callback.h 是我在原问题中贴出的代码。这一切都通过 swig、c++ 和 Java 链编译而没有错误,但看起来 swig 在连接 c++ 和 Java 之间的点时遇到了一些麻烦。它创建了这三个 类:
SWIGTYPE_p_f_std__function__f_std__shared_ptr__some__ns__TheThing____void____void
SWIGTYPE_p_f_std__shared_ptr__some__ns__TheThing____void
SWIGTYPE_p_std__functionT_void_fstd__shared_ptrT_some__ns__TheThing_tF_t
不幸的是,简单 Java 主代码中的大多数方法现在都采用或 return 这些对象,这使得它们相当不可用。知道如何解决这个问题吗?谢谢!
更详细一点以确保完整性:我正在使用以下三个脚本来编译和 运行 代码。参数略有不同,但我认为这不重要。在我这边,它被设置为一个 Eclipse maven 项目。这些脚本位于我项目的根目录中,头文件和 swig 文件位于 src/main/resources,java 源文件位于 src/main/java,java 已编译 类 位于在 target/classes。 Eclipse 执行 java 编译。
swigthing.sh
MODULE_NAME=Thing
PACKAGE=some.ns
OUTDIR=./src/main/java/some/ns
I_FILE=./src/main/resources/func_thing_test.i
mvn clean
rm $OUTDIR/*.*
mkdir -p $OUTDIR
swig -java -c++ -module $MODULE_NAME -package $PACKAGE -outdir $OUTDIR $I_FILE
./compileThingSwigTest.sh
compileThingSwigTest.sh
#!/bin/bash
pushd src/main/resources
g++ -c -std=gnu++11 -fpic \
func_thing_test_wrap.cxx \
-I/usr/lib/jvm/java/include \
-I/usr/lib/jvm/java/include/linux
g++ -shared func_thing_test_wrap.o -o libFunc.so
popd
runThingTest.sh
pushd target/classes
java -Xmx512M -Xms512M -Djava.library.path=. some.ns.test.RunThingTest
popd
最后更新
修复了上面的代码以将正确的参数传递给 std_function。现在在问题和答案之间有一个完整的工作示例,说明我所追求的。
虽然 SWIG 本身不提供 std_function.i,但我们可以通过一些工作自己构建一个。我在这里的回答是我的 a previous of mine answer 的更通用版本,针对特定实例查看此问题并针对 Python。我将对其进行多次迭代,为通用 std::function
包装定义一个 %std_function
宏。
我假设您希望通过包装 std::function
实现四件事,这成为我们的主要要求:
- 我们希望能够从 Java 代码中调用
std::function
个对象。 - 包装的
std::function
对象需要像任何其他对象一样传递,包括在任一方向跨越语言边界。 - 应该可以在 Java 中编写
std::function
个对象,这些对象可以传回 C++,而无需修改现有的std::function
个对象的 C++ 代码(即维护std::function
跨语言的类型擦除) - 我们应该能够使用指向函数类型的 C++ 指针在 Java 中构造
std::function
个对象。
我将完成这些工作并展示我们如何实现这一目标。在可能的情况下,我也会让解决方案语言不可知。
出于讨论的目的,我对你问题的 shared_ptr
部分进行了掩饰,它实际上并没有改变什么,因为你已经 shared_ptr
工作了,这实际上足以使用在这种情况下也是如此,它只会使我的示例变得不必要地冗长。
我正在努力的解决方案实际上是根据 SWIG 中现有的 shared_ptr
支持建模的。我整理了一个测试界面来说明如何使用它:
%module test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
%}
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
%}
基本上,要使用它,您需要做的就是包含文件 "std_function.i",然后使用宏 %std_function
,其参数为:
%std_function(Name, Ret, ...)
每次实例化要包装的 std::function
模板时调用一次,其中 Name
是您要在 Java 中调用的类型,Ret 是 return 类型,然后剩余的(可变参数)参数是您函数的输入。所以在我上面的测试界面中,我基本上是想包装 std::function<void(int,double)>
.
编写 "std_function.i" 的第一个版本实际上并不太难。获得基本工作要求 #1 和 #2 所需要做的就是:
%{
#include <functional>
%}
%define %std_function(Name, Ret, ...)
%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();
namespace std {
struct function<Ret(__VA_ARGS__)> {
// Copy constructor
function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);
// Call operator
Ret operator()(__VA_ARGS__) const;
};
}
%enddef
这在生成的包装器代码中包含一次 C++ 头文件,然后设置宏以在接口中使用。 SWIG 支持使用 C99 可变宏参数(得到更好的支持)的 C++11 variadic templates isn't actually very helpful for us in this usage scenario, so the macro I wrote basically re-implements the default template expansion functionality。巧合的是,这意味着我们正在编写的 SWIG 代码将适用于 2.x 甚至某些 1.3.x 版本。 (我用 2.x 测试过)。即使 if/when 你的 SWIG 版本确实有 %template
支持与 std::function
一起工作,保留这个宏对于使其实际可调用的其余胶水仍然有用。
std:function
模板的手动扩展仅限于我们关心的用法:实际的 operator()
和可能派上用场的复制构造函数。
唯一要做的就是将 operator()
重命名为与目标语言匹配的名称,例如Java 将其重命名为一个名为 "call" 的常规函数,或者如果您将 Python 定位为 __call__
或在需要时使用 tp_slots。
这足以让我们的界面工作,为了演示它我写了一点 Java:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
}
}
我编译的是:
swig2.0 -Wall -c++ -java test.i
g++ -Wall -Wextra -std=c++11 test_wrap.cxx -o libtest.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux -shared -fPIC
javac run.java
LD_LIBRARY_PATH=. java run
成功了。
此时要求 #4 很容易从列表中划掉。我们需要做的就是告诉 SWIG std::function
中还有另一个构造函数,它接受兼容的函数指针:
// Conversion constructor from function pointer
function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));
然后我们可以在 SWIG 中将其与 %callback
mechanism 一起使用,我们的测试接口文件变为:
%module test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
void add_and_print(int a, double b) {
std::cout << a+b << "\n";
}
%}
%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
%}
然后我们用来称呼它的 Java 是:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
new Functor(test.add_and_print_cb).call(3,4.5);
}
}
此时我们编译和 运行 同样成功。
(请注意,此时看到一些以名称 "SWIGTYPE_p_f_..." 开头的 Java classes 是正常且可取的 - 这些包含 "pointer to function" 类型由指向函数构造函数和回调常量的指针使用)
要求 #3 是事情开始变得棘手的地方。从本质上讲,我们遇到了与我之前回答的问题相同的问题 on making SWIG generate an interface in Java,除了现在我们希望在更通用的宏中完成它。
事实证明,在这种情况下,因为我们想要生成的界面相当简单,所以我们可以在宏中使用一些技巧让 SWIG 为我们生成它。
为了完成这项工作,我们需要做的主要事情是设置 SWIG 控制器以提供跨语言多态性,并允许用 Java 编写的内容实现 C++ 接口。这是我代码中生成的后缀为"Impl"的class。
为了让 "feel right" 给 Java 开发人员,我们希望 C++ 和 Java 实现的 std::function
对象仍然使用相同的类型。即使 std::function::operator()
是虚拟的,我们仍然不希望 SWIG 主管直接使用该类型,因为按值传递 std::function
很常见,这会导致类型 slicing problems。因此,当 Java 开发人员扩展我们的 std::function
对象并覆盖 call
时,我们需要做一些额外的工作来实现它,以便使用该对象的 C++ 实际上调用 Java 实现考虑到我们不能只使用 directors 来自动处理这个问题。
所以我们所做的看起来有点奇怪。如果你构造一个 Java 对象来实现 std::function
那么有一个特殊的,受保护的构造函数。此构造函数保留 swigCPtr
成员变量,该变量通常指向一个真正的 C++ 对象 0,而是创建一个实现 "Impl" 接口的匿名包装对象,并简单地将所有内容代理回 call
Java 对象的成员。
我们还有另一个应用类型映射,在 Java 中,我们将 std::function
对象传递给 C++ 的任何地方。它的作用是检测我们有哪种情况 - C++ 实现的 std::function
对象,或 Java 对象。在 C++ 的情况下,它没有做任何特别的事情,一切都照常进行。在 Java 情况下,它获取代理对象并要求 C++ 将其转换回另一个单独的 std::function
实例,该实例被替换。
这足以让我们在两种语言中获得我们想要的行为,而没有任何两边感觉奇怪的东西(除了大量机械提升之外 t运行sparently)。
这里要注意的是,自动构造代理对象并非易事。 Java 有 dynamic proxy classes 作为反射 API 的一部分,但这些只实现接口,不扩展抽象 classes。我确实尝试使用的一种可能性是 Java 端的 void call(Object ...args)
,这是一个可变函数参数。虽然合法,但这似乎并没有真正覆盖 super class 中需要的任何情况。
我最终做的是调整 some macros 以我想要的方式迭代可变宏参数。这是一个相当明智的解决方案,因为我们已经出于其他原因决定使用可变参数 C99 宏参数。这种机制在我的解决方案中总共使用了四次,一次在函数声明中,一次在 Java 和 C++ 的委托调用中。 (C++ 保留了完美的转发属性,Java 需要执行类型映射查找,因此它们在每种情况下都是不同的)。
还有一个自定义类型映射来简化一些 Java 代码 - 在 void 函数中编写 return other_void_function();
是不合法的,所以我们需要特殊情况下的 void 函数,如果它不是'
让我们看看现实中的样子。首先是我用于测试的 run.java,它只是对前面的示例稍作修改,添加了 std::function
对象的 Java 实现。
public class run extends Functor {
public static void main(String[] argv) {
System.loadLibrary("test");
test.make_functor().call(1,2.5);
new Functor(test.add_and_print_cb).call(3,4.5);
Functor f = new run();
test.do_things(f);
}
@Override
public void call(int a, double b) {
System.out.println("Java: " + a + ", " + b);
}
}
std_function.i 现在有了上面列出的所有更改后显着变大:
%{
#include <functional>
#include <iostream>
#ifndef SWIG_DIRECTORS
#error "Directors must be enabled in your SWIG module for std_function.i to work correctly"
#endif
%}
// These are the things we actually use
#define param(num,type) $typemap(jstype,type) arg ## num
#define unpack(num,type) arg##num
#define lvalref(num,type) type&& arg##num
#define forward(num,type) std::forward<type>(arg##num)
// This is the mechanics
#define FE_0(...)
#define FE_1(action,a1) action(0,a1)
#define FE_2(action,a1,a2) action(0,a1), action(1,a2)
#define FE_3(action,a1,a2,a3) action(0,a1), action(1,a2), action(2,a3)
#define FE_4(action,a1,a2,a3,a4) action(0,a1), action(1,a2), action(2,a3), action(3,a4)
#define FE_5(action,a1,a2,a3,a4,a5) action(0,a1), action(1,a2), action(2,a3), action(3,a4), action(4,a5)
#define GET_MACRO(_1,_2,_3,_4,_5,NAME,...) NAME
%define FOR_EACH(action,...)
GET_MACRO(__VA_ARGS__, FE_5, FE_4, FE_3, FE_2, FE_1, FE_0)(action,__VA_ARGS__)
%enddef
%define %std_function(Name, Ret, ...)
%feature("director") Name##Impl;
%typemap(javaclassmodifiers) Name##Impl "abstract class";
%{
struct Name##Impl {
virtual ~Name##Impl() {}
virtual Ret call(__VA_ARGS__) = 0;
};
%}
%javamethodmodifiers Name##Impl::call "abstract protected";
%typemap(javaout) Ret Name##Impl::call ";" // Suppress the body of the abstract method
struct Name##Impl {
virtual ~Name##Impl();
protected:
virtual Ret call(__VA_ARGS__) = 0;
};
%typemap(maybereturn) SWIGTYPE "return ";
%typemap(maybereturn) void "";
%typemap(javain) std::function<Ret(__VA_ARGS__)> "$javaclassname.getCPtr($javaclassname.makeNative($javainput))"
%typemap(javacode) std::function<Ret(__VA_ARGS__)> %{
protected Name() {
wrapper = new Name##Impl(){
public $typemap(jstype, Ret) call(FOR_EACH(param, __VA_ARGS__)) {
$typemap(maybereturn, Ret)Name.this.call(FOR_EACH(unpack, __VA_ARGS__));
}
};
proxy = new $javaclassname(wrapper);
}
static $javaclassname makeNative($javaclassname in) {
if (null == in.wrapper) return in;
return in.proxy;
}
// Bot of these are retained to prevent garbage collection from happenign to early
private Name##Impl wrapper;
private $javaclassname proxy;
%}
%rename(Name) std::function<Ret(__VA_ARGS__)>;
%rename(call) std::function<Ret(__VA_ARGS__)>::operator();
namespace std {
struct function<Ret(__VA_ARGS__)> {
// Copy constructor
function<Ret(__VA_ARGS__)>(const std::function<Ret(__VA_ARGS__)>&);
// Call operator
Ret operator()(__VA_ARGS__) const;
// Conversion constructor from function pointer
function<Ret(__VA_ARGS__)>(Ret(*const)(__VA_ARGS__));
%extend {
function<Ret(__VA_ARGS__)>(Name##Impl *in) {
return new std::function<Ret(__VA_ARGS__)>([=](FOR_EACH(lvalref,__VA_ARGS__)){
return in->call(FOR_EACH(forward,__VA_ARGS__));
});
}
}
};
}
%enddef
并且 test.i 稍微扩展以验证 Java -> C++ 传递 std::function
对象并启用控制器:
%module(directors="1") test
%include "std_function.i"
%std_function(Functor, void, int, double);
%{
#include <iostream>
void add_and_print(int a, double b) {
std::cout << a+b << "\n";
}
%}
%callback("%s_cb");
void add_and_print(int a, double b);
%nocallback;
%inline %{
std::function<void(int,double)> make_functor() {
return [](int x, double y){
std::cout << x << ", " << y << "\n";
};
}
void do_things(std::function<void(int,double)> in) {
in(-1,666.6);
}
%}
此编译和 运行 与前面的示例一样。值得注意的是,我们已经编写了很多 Java 特定代码——尽管如果您的目标是 Python,该设计也适用于其他语言,但使用 [= 解决其中一些问题要简单得多=227=] 具体功能。
有两点我想改进:
使用 C++14 可变参数 lambda 来避免我用来保持它们与 C++11 兼容的宏预处理器魔法。如果你有 C++ 14,
%extend
构造函数变为:%extend { function<Ret(__VA_ARGS__)>(Name##Impl *in) { return new std::function<Ret(__VA_ARGS__)>([=](auto&& ...param){ return in->call(std::forward<decltype(param)>(param)...); }); } }
当按照预期将此宏与 std::shared_ptr
一起使用时,宏本身不需要更改。然而,应用的 javadirectorin 和 directorin 类型映射的实现存在问题,这确实阻止了 "just working"。即使使用来自 "trunk" 的 SWIG 构建也是如此。 (combining directors and shared_ptr 上有一个悬而未决的问题)
我们可以解决这个问题,方法是在调用 %shared_ptr
:
%shared_ptr(some::ns::TheThing);
%typemap(javadirectorin) std::shared_ptr<some::ns::TheThing> "new $typemap(jstype, some::ns::TheThing)(,false)";
%typemap(directorin,descriptor="L$typemap(jstype, some::ns::TheThing);") std::shared_ptr<some::ns::TheThing> %{
*($&1_type*)&j = &;
%}
这两个类型映射中的第一个实际上是死代码,因为我们在我们的抽象class中强制"call"方法是抽象的,但是修复这个方法的编译比它更容易压制它。第二个类型图很重要。它与普通的 "out" 类型映射非常相似,因为它创建了一个 jlong
,它实际上只是 C++ 指针的表示,即它准备了一个从 C++ 到 Java 的对象。
请注意,如果您在模块中使用包,则可能需要修改 directorin 类型映射的描述符属性,"L$packagepath/$typemap(...);"
或直接手动编写。
这也应该删除现在生成的虚假 "SWIGTYPE_p_sstd__shared_ptr..." 类型。如果您有 return shared_ptr 对象的虚函数,您也需要为它们编写 directorout 和 javadirectorout 类型映射。这些可以基于正常的 "in" 类型映射。
这足以让我自己使用修改后的 Functor
进行简单测试,至少对于我今天从 t运行k 检出的 SWIG 版本来说是这样。 (我对 2.0.x 的测试失败了,我没有付出太多努力让它工作,因为这是一个已知的正在进行的工作)。