Nimと他の言語の連携方法のまとめです。
Nimは他の言語との連携がしやすく、連携できる言語も多いのでとても便利です。
ただ、連携といっても他言語を直接Nimから呼び出すものもあれば、Nimを他言語にコンパイルして連携するものもあるので注意が必要です。
連携できる言語として確認しているものは以下の通りです
- C
- C++
- Javascript
- Objective-C
- Java
それぞれの連携方法をまとめていきたいと思います。
基本
基本的にはpragmaを使って連携していく形になります。
その他の基本事項として、emitプラグマでソースを直接埋め込みができます。
例:
{.emit: """
static int cvariable = 420;
""".}
C
Nimは基本的にCを通してコンパイルする(いわゆるトランスパイラ)ので、Cコンパイラの最適化の恩恵を受けられるだけでなく、Cの資産との相性もとても良いです。
importc
importcプラグマを使ってCの関数を宣言します。
試しにprintfをインポートしてみます。
proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}
dynlib
dynlibプラグマを利用することによってヘッダが用意できなくても、共有ライブラリから関数をインポートすることができます。
まずCソースを用意して、
// file: testshared.c
int add5(int x) {
return x + 5;
}
共有ライブラリとしてコンパイルし、
$ gcc -shared -o testshared.dll testshared.c
Nimからインポートできます。
proc add5(x: int): int {.dynlib: "testshared.dll", importc: "add5".}
echo add5(2) # output: 7
先ほどの例はwindowsを対象としましたが、他のプラットフォームへの対応も簡単です。
whenによってコンパイル時にプラットフォームによって読み込む共有ライブラリの名前を切り替えることができます。
when defined(windows):
const sharedname = "testshared.dll"
elif defined(macosx):
const sharedname = "testshared.dylib"
elif defined(unix):
const sharedname = "testshared.so"
proc add5(x: int): int {.dynlib: sharedname, importc: "add5".}
exportc
exportcプラグマを使うことによってCからNimの関数を呼び出すことも可能です。
Nim側
# file: fib.nim
proc fib(a: cint): cint {.exportc.} =
if a <= 2:
result = 1
else:
result = fib(a - 1) + fib(a - 2)
上記のNimソースをCへコンパイルし、
$ nim c --noMain --noLinking --header:fib.h fib.nim
C側
// file: export_test.c
#include "fib.h"
#include <stdio.h>
int main() {
NimMain();
printf("%d\n", fib(10)); // output: 55
}
上記のCソースと共にコンパイルすることができます。
$ gcc -o export_test.exe -Inimcache -I"Nimコンパイラのlibディレクトリのパス" nimcache/*.c exportc_test.c
exportcはprefixを付けることもでき、さらにpragmaは自分で定義することもできるので、
{.pragma: exportmylib, exportc: "mylib_$1".}
proc add5(x: int): int {.exportmylib.} =
return x + 5
proc add10(x: int): int {.exportmylib.} =
return x + 10
でC側からmylib_add5(2)
のような形で呼び出せるようにすることも可能です。
c2nim
Nimにはc2nimというツールがあり、Cソースから自動でbindingを生成してくれます。
上記で挙げた宣言を手動でする必要がなくなるので、大規模なCライブラリの場合かなり楽ができます。
特筆するべき点として、Cプリプロセッサ(いわゆるマクロ)もNimへ変換してくれるので、Cの他言語へのバインディングでありがちな「マクロ使わない場合どう記述すればいいんだ」的な面倒くささが減ります。
// file: c2nim_test.c
#include "c2nim_test.h"
int my_add(int x) {
return x + LUCKY_NUMBER;
}
// file: c2nim_test.h
#define LUCKY_NUMBER 777
#define my_add5(x) my_add(x) + 5
int my_add(int x);
$ c2nim c2nim_test.h
でCヘッダをNimに変換できます。
# file: c2nim_test.nim
const
LUCKY_NUMBER* = 777
template my_add5*(x: untyped): untyped =
my_add(x) + 5
proc my_add*(x: cint): cint
いろいろ細かい制御もできるのでバインディング作る際は使うと楽ができるかもしれません。
C++
Nimではプログラミング言語としては珍しく、公式でC++との連携をサポートしています。
具体的にはNimをC++にコンパイルすることによって実現しています。
基本的にはimportcppプラグマとemitプラグマを駆使してnewやネームスペースに対応していく形になります。
// file: myint.cpp
class MyInt {
public:
int num;
MyInt(int x): num(x) {}
void inc() {
num++;
}
int get() {
return num;
}
};
# file: importcpp_test.nim
const myintsrc = staticRead("myint.cpp") # C++ソースをコンパイル時に読み込み
{.emit: myintsrc.} # 読み込んだC++ソースを埋め込み
type
MyIntObj {.importcpp: "MyInt", nodecl.} = object
MyInt = ptr MyIntObj
proc newMyInt(x: cint): MyInt {.importcpp: "new MyInt(@)", nodecl.}
proc inc(this: MyInt) {.importcpp: "#.inc(@)", nodecl.}
proc get(this: MyInt): int {.importcpp: "#.get(@)", nodecl.}
var mi = newMyInt(5)
mi.inc()
mi.inc()
echo mi.get()
$ nim cpp importcpp_test.nim
実は、公式のマニュアルにあるサンプルは自分の環境(0.15.2)では動かなくて、importcppに加え、nodeclプラグマを付ければ動きました。
Javascript
NimはJavaScriptに変換できるので、AltJS的な使い方もできます。
NimからJSを呼び出す例
<html>
<body>
<script type="text/javascript">
function addTwoIntegers(a, b) {
return a + b;
}
</script>
</body>
</html>
# file: calculator.nim
proc addTwoIntegers(a, b: int): int {.importc.}
when isMainModule:
echo addTwoIntegers(3, 7)
$ nim js -o:calculator.js calculator.nim
JSからNimを呼び出す例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Nim lang fib</title>
<script type="text/javascript" src="fib.js"></script>
<script type="text/javascript">
alert("Fib for 9 is " + fib(9));
</script>
</head>
<body>
</body>
</html>
# file: fib.nim
proc fib(a: cint): cint {.exportc.} =
if a <= 2:
result = 1
else:
result = fib(a - 1) + fib(a - 2)
$ nim js -o:fib.js fib.nim
DOM操作
ブラウザでjavascriptを動かす場合、やりたいのは殆どの場合DOM操作だと思いますが、Nimには標準ライブラリにDOM操作用のライブラリであるdom
があるのでこれで楽ができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Nim lang Hello World</title>
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
# file: main.nim
import dom
document.addEventListener("DOMContentLoaded") do (e: Event):
document.getElementById("app").innerHTML = "Hello from Nim World!"
$ nim js -o:main.js main.nim
Node.js
JSへのコンパイル時に-d:nodejs
をつけることでNode.js向けの出力をすることもできます。
$ nim js -d:nodejs -r examples/hello.nim
Objective-C
自分はObjective-Cを動かせる環境を所有していないので、公式のサンプルを少しいじってお茶を濁しておきます。
// file: objcsrc.m
#include <objc/Object.h>
@interface Greeter:Object
{
}
- (void)greet:(long)x y:(long)dummy;
@end
#include <stdio.h>
@implementation Greeter
- (void)greet:(long)x y:(long)dummy
{
printf("Hello, World!\n");
}
@end
#include <stdlib.h>
{.passL: "-lobjc".}
const objcsrc = staticRead("objcsrc.m")
{.emit: objcsrc.}
type
Id {.importc: "id", header: "<objc/Object.h>", final.} = distinct int
proc newGreeter: Id {.importobjc: "Greeter new", nodecl.}
proc greet(self: Id, x, y: int) {.importobjc: "greet", nodecl.}
proc free(self: Id) {.importobjc: "free", nodecl.}
var g = newGreeter()
g.greet(12, 34)
g.free()
Java
Javaとの連携はjnimという外部ライブラリを使用することで可能です。
$ nimble install jnim
import jnim
{.passC: "-I\"path/to/jdk/include\"".}
{.passC: "-I\"path/to/jdk/include/win32\"".}
jclass java.io.PrintStream of JVMObject:
proc println(s: string)
jclass java.lang.System of JVMObject:
proc `out`: PrintStream {.prop, final, `static`.}
initJNI()
System.`out`.println("This string is printed with System.out.println!")
まとめ
後半のObjective-CとJavaはかなり早足になってしまいました。
Nimは色々な言語と連携ができるので一部の遅い部分をNimで書くなど小さく始める手も有りだと思います。