uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

Groovy+JNA

JNA(Java Native Access)というものがあります(https://jna.dev.java.net/)。これはJavaから動的にDLL中の関数を呼び出すものです。「動的に」ということの意味は、しち面倒くさくバグも作り出しやすい、悪名高きJNI(Java Native Interface)を使わずに、そもそもCコンパイラなどを一切使わずに、DLL(Unix環境ではso)に含まれる関数を呼び出すことができるということです。jnaの配布物(jna.jar)にはさまざまなOS用のディスパッチャが含まれているので、プラットフォーム独立かのように錯覚するぐらいです*1

ということで、動的つながり(強引)ということで、Groovyから呼んで見ます。

jna.jarをCLASSPATHに通せば*2たとえば、Pure Javaだけではできない「カレントディレクトリを移動する」という操作を

@Grab('net.java.dev.jna:jna:3.2.2')
import com.sun.jna.*;
import com.sun.jna.win32.*;

interface CLibrary extends Library {
  CLibrary INSTANCE = Native.loadLibrary("msvcrt", CLibrary.class);
   int _chdir(String dir);
}

CLibrary.INSTANCE._chdir(args[0])
println "pwd".execute().text

はいこのとおり。これはWindowsの場合であり、_chdir関数はmsvcrt.dllから読み込みます。
Windowsの場合、Win32APIを直たたきで

@Grab('net.java.dev.jna:jna:3.2.2')
import com.sun.jna.*;
import com.sun.jna.win32.*;

interface Kernel32Library extends StdCallLibrary {
  Kernel32Library INSTANCE = Native.loadLibrary("kernel32", Kernel32Library.class);
  boolean SetCurrentDirectoryA(String dir);
}

Kernel32Library.INSTANCE.SetCurrentDirectoryA(args[0])
println "pwd".execute().text

でもオーケーです。
MacOS X(あるいはUnix系OS)なら

@Grab('net.java.dev.jna:jna:3.2.2')

import com.sun.jna.*;
import com.sun.jna.win32.*;

interface CLibrary extends Library {
  CLibrary INSTANCE = Native.loadLibrary("c", CLibrary.class);
   int chdir(String dir);
}

CLibrary.INSTANCE.chdir(args[0])
println "pwd".execute().text

のようになります。

Cで通常呼び出す関数名(chdir,SetCurrentDirectory)と、実際にdllに含まれているDLLのエントリファンクション名(_chdir, SetCurrentDirectoryA)が異なっているときがあるというのは要注意。C言語からはマクロとかで変換されています。

上では、インターフェース定義を経由していますが、「GroovyならMOPがあるからいらないんじゃないかな?」とだめ元で直に呼んでやってみたらダメでした。たぶんJNAでは対象メソッドの呼び出しに動的Proxyを使ってると思うので、原理的に駄目でしょう。GroovyならMOPを使って解析して呼ぶこともできるようにすることもできるかもしれません。

*1:もちろん、プラットフォームごとに関数自体の有無がことなり仕様もことなるのでプラットフォーム依存であることには変わりません。でもたとえば、C標準関数の範囲なら有る程度はいけるかも

*2:以下ではGrabしてるのでダウンロード/インストールも不要です。