最近需要对好几个C/C++的库做封装,感觉趟了好多坑…
之前只用过 CxxWrap.jl,我自己的体验是,如果库本身自带了Python的binding,用 CxxWrap.jl 还算行(<=Python的工作量)。昨天需要对 deepmind/hanabi-learning-environment 这个库做封装,初看了下代码,感觉比较适合用 Clang.jl。于是乎:
1. 先用BinaryBuilder.jl 打包
- 本地Ubuntu测试的时候,一开始没有加
sudo
(我清楚记得以前会提示的,后来不知道为什么没有提示了),一直出错。加上之后,本地编译正常了。- 原始的代码库 deepmind/hanabi-learning-environment 的
CMakeFiles.txt
里,没有install
的步骤,手动fork之后自己添加了相应的install,这样BinaryBuilder
运行后才会在products
目录中有编译结果 (需要的话把一些.h
文件也打进去) - 这个库本身没有太多依赖,相应的builder比较简单
- 原始的代码库 deepmind/hanabi-learning-environment 的
Q1: 我指定
platforms = supported_platforms()
之后,travis的编译会提示MacOS没有授权之类的,会导致出错,那 @Gnimuc 写的 NuklearBuilder.jl 这种包含 MacOS 的release怎么生成的?
2. 创建Julia Package
然后是创建 Hanabi.jl,重命名上面生成的 build_XXX.jl
文件为 deps/build.jl
, 用于安装包的时候生成deps.jl
文件。然后创建 gen
目录,用Clang.jl
生成相应的wrapper:
参照 Clang.jl提供的例子执行之后,会得到一个LibTemplate.jl
的文件,这个模板需要稍加修改,目前都是针对单个header文件写死了的。
Q2:执行过程中会报错:不知道有没有影响?
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:91:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:92:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:93:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:94:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:131:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:132:1: error: unknown type name 'bool'
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:180:1: error: unknown type name 'bool'
Q3: 原始的
pyhanabi.h
是包含extern C {}
语句的,我手动去掉了之后调用的Clang,不去掉的话就会报错
/home/tj/data/workspace/github/Hanabi/deps/usr/include/pyhanabi.h:24:8: error: expected identifier or '('
似乎是parser的原因?
Q4: 一般什么时候用 Clang.jl Cxx.jl CxxWrap.jl 呢?
我现在还需要封装另外两个库:
- GitHub - erincatto/box2d: Box2D is a 2D physics engine for games
- GitHub - bulletphysics/bullet3: Bullet Physics SDK: real-time collision detection and multi-physics simulation for VR, games, visual effects, robotics, machine learning etc.
不确定应该采用哪种方式比较合适,不知道有没有什么通用的建议?
Q5: 如何理解
Ptr{T}
跟Ref{T}
?
道理我都懂,文档里也写了(PS: 这部分中文文档似乎缺失了)
https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#When-to-use-T,-Ptr{T}-and-Ref{T}-1
In Julia code wrapping calls to external C routines, ordinary (non-pointer) data should be declared to be of type
T
inside theccall
, as they are passed by value. For C code accepting pointers,Ref{T}
should generally be used for the types of input arguments, allowing the use of pointers to memory managed by either Julia or C through the implicit call toBase.cconvert
. In contrast, pointers returned by the C function called should be declared to be of output typePtr{T}
, reflecting that the memory pointed to is managed by C only. Pointers contained in C structs should be represented as fields of typePtr{T}
within the corresponding Julia struct types designed to mimic the internal structure of corresponding C structs.
In Julia code wrapping calls to external Fortran routines, all input arguments should be declared as of type
Ref{T}
, as Fortran passes all variables by pointers to memory locations. The return type should either beCvoid
for Fortran subroutines, or aT
for Fortran functions returning the typeT
.
上面这段话的大意是说, Ref{T}
类型是Julia分配的,用于GC的管理,而Ptr{T}
是C管理的,在:ccall
的时候前者会自动转换。但我不太确定下面的这种写法是正确的:
比如,下面这段通过Clang自动生成的libhanabi_common.jl
中的一部分:
我在Julia中构造该结构体的时候,目前是这样写的:
game = Ref{Ptr{Cvoid}}()
g = PyHanabiGame(game[])
然后为了调用下面的接口函数:
我需要再把上面的封装起来:
my_game = Ref(g)
n = NumPlayers(my_game[])
才能得到正常的结果。 上面这种写法参考的以下内容:
- Calling C and Fortran Code · The Julia Language
- the proper way to declare C void pointers in Julia - Stack Overflow
但是我不确定这样子是否合理,因为中间还生成了好几个Ref