Help us understand the problem. What is going on with this article?

Nimで他の言語との連携方法まとめ

More than 3 years have passed since last update.

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で書くなど小さく始める手も有りだと思います。

nnn-school
IT×グローバル社会を生き抜く“創造力”を身につけ、世界で活躍する人材を育成する。
https://nnn.ed.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした