JNI FFI

转载 2015年07月10日 14:19:57

近几年来各种编程语言一直像雨后春笋一样不断出现,程序员们总是在不断寻找称心如意的编程利器,然而历史上却始终未出现一招鲜吃遍天的编程语言,一门编程语言总有其擅长的和不擅长的场合,不仅仅是由于编程语言的语法层面同时也和编程语言运行时的设计思路、平台的支持有着莫大的关系。于是在实际的编程实践中使用合适的编程语言干合适的事才是正确的选择。但当我们运用多种语言解决问题时却不得不面对一个困难的问题,即跨编程语言间的互相调用。

编程语言间的互相调用可以有几种方案:

  • 通过RPC调用来实现跨语言的调用,不同语言编写的程序运行在不同的进程中,通过网络、输入输出流、管道等设施。
  • 通过共享内存实现进程间通信
  • 通过FFI(Foreign Function Interface)机制,通过提供运行时兼容的设施(一般是lib及运行时支持)实现在同一进程内跨越语言的边界调用函数,例如Java的JNI就是这样一种设施

以上几种方案中,使用FFI是一种较为高效轻量级的方案,是真正意义上做到两种不同语言运行时混合协作的方案,当前大部分编程语言都提供了FFI机制使得可以跨越运行时边界调用C/C++编写的程序(以下称作native函数),JNI可能是大家最为熟悉的FFI机制,在Android,Java服务端编程中都有广泛的使用,但要用好JNI做到不踩坑则有必要理解FFI机制背后的原理。

FFI机制最重要的是解决不同运行时间内存管理的问题,以JNI为例,Java语言的内存管理采用垃圾收集机制,而C语言则是依赖于显示的申请释放内存,可以想象当Java调用C函数传递由Java运行时托管的对象时,由于对象指针被C运行时引用时Java运行时一端将无法获知对象被引用的情况,从而垃圾收集器将无法判断该对象是否被引用从而无法将其释放,那么JNI是怎样解决这一问题的呢。

首先JNI引入了local reference的概念,当调用native方法时jvm会在调用者线程堆栈中开辟一个frame来持有传入的参数对象的引用,java的垃圾收集(GC)是以一系列的root set为起点扫描对象是否被引用的,而这个frame便保障了GC能够发现传入native的参数处于被引用状态,从而不会被收集,当native函数返回时这些引用也自然被释放。另一方面native函数中也可创建对象的local reference也会保存在frame中,从而使得对象的声命周期被jvm托管,在native函数中创建的对象除非作为返回值返回获得新的引用,否则当从native返回后由于local reference被释放则对象的生命周期结束。然而由于jvm的垃圾收集不是实时发生,所以在native函数中大量创建local对象而依靠垃圾收集并不可取,应考虑显式调用DeleteLocalRef在native中完成释放。

local reference生命周期的特点使得local对象不能被native运行时持有,因此需要有更长生命周期的机制来实现需求,JNI中则是引入了生命周期完全可控的global reference,可以猜到global reference创建后应保存在jvm全局的root set中,确保对象既能在java中被访问又不会被GC回收,仅当在native中显式调用DeleteGlobalRef释放对象。

通过引入local/global reference能够解决跨运行时对象声明周期控制的问题,但问题到这里还没解决完。下面我们来看一个问题,以Java为例我们常用的垃圾收集器在垃圾收集时会搬动内存(copy收集、mark-compact收集均是如此),因此垃圾收集过程中需要修改指向内存块的指针,然而在使用JNI的情况下一旦进入了native的运行时垃圾收集器便无能为力了,因此我们在JNI中持有的reference一般是间接指向的,但某些情况下间接指向并不能满足需求,例如从Java运行时调用native函数时传入一个Array对象,API的signature如下:

    NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, 
    ArrayType array, jboolean *isCopy);

可以看到API需要返回一个Native类型的Array给native访问,这时就不适合使用间接的引用了,为解决这一问题API带了一个isCopy参数在Oracle的jvm中该参数为非空时API的行为将是copy整个Array,为空时则为直接引用,但如果直接引用了内存则意味着jvm中垃圾收集时将无法搬动该内存块,从而将对垃圾收集性能产生影响,所以一般的选择都是选择copy行为。在选择copy的情况下如果想要对Array进行更新该怎么做呢,我们再来看一个API

    void Release<PrimitiveType>ArrayElements(JNIEnv *env, 
    ArrayType array, NativeType *elems, jint mode);

这里有一个mode参数,这个参数便是告诉运行时要如何将我们修改过的copy更新到原对象

mode        actions
0            copy back the content and free the elems buffer
JNI_COMMIT    copy back the content but do not free the elems buffer
JNI_ABORT    free the buffer without copying back the possible changes

可以看到mode为0或JNI_COMMIT时便能将变动通过copy的方式更新到原对象。当然这里一般需要程序保证从copy到release期间原对象不会被修改。

至此为止FFI设计最重要的环节--解决不同运行时间内存管理的问题基本解决了,JNI使用的这些方案也是大部分FFI采用的典型设计方案,这样当需要使用其他编程语言FFI机制时也就不再有什么神秘的黑魔法了。那是否还有非典型的FFI呢,大家不妨去了解一下Rust语言,看看没有运行时这一负担的情况下FFI会变得多么轻巧,而本文的讨论就到此结束了。

相关文章推荐

S29GL256N11FFI010闪存FLASH资料

  • 2013-07-18 10:37
  • 2.57MB
  • 下载

FFI.rar

  • 2011-10-23 13:06
  • 1.28MB
  • 下载

FFI::NotFoundError: Function '_get_errno' not found in [msvcrt.dll]解决办法

案例: require 'watir'后,第一次执行 ie=Watir::IE.new报错如下: C:\>irb irb(main):001:0> require 'watir' => tru...

Ruby FFI - Ruby调用C库最棒的工具

Ruby FFI - Ruby调用C库最棒的工具 2008-11-02 00:40 by 见习编辑 robbin 评论(6) 有4162人浏览 Ruby C++ C C# 声...

Lua FFI 实战

转自:http://blog.csdn.net/weiwangchao_/article/details/16880401 由来 FFI库,是LuaJIT中最重要的一个扩展库。它允...

luajit FFI简单使用(1)

本文参考了luajit的[官方文档](http://luajit.org/ext_ffi.html)编写。文档的结构也基本上是按照官网的来。

CLisp 13:使用包FFI在CLisp中调用C语言写的函数

编译一个LISP文件,通常得到*.fas和*.lib文件,当LISP文件用到package FFI中的函数时,会生成一个*.c文件 clisp\clisp.exe -K full -c source...

LuaJIT通过ffi调用win32 API完成窗口版HelloWorld示例

看网上关于LuaJIT调用Win32 API函数的示例除了蹦对话框,就是调一些简单的修改标题之类的函数来演示…… 就不能绘制一个完整的窗口,有一个完整的消息循环么? 所以说,这种没人去干的事,当然就有...

luajit笔记---编译成静态库以及FFI绑定宿主程序函数

本以为可以像lua一样把代码丢进去直接编译就好了,结果发现luajit有一堆汇编代码,不知道怎么处理,后来一搜索才知道luajit本身提高的批处理也可以编译成静态库,就是在后面加个static,郁闷到...

ffi超级巡警

  • 2012-07-13 14:56
  • 1.25MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)