Open4

IntelliJ IDEA / Android StudioでCのコードをKotlinに変換する

Atsushi EnoAtsushi Eno

自分のMIDI 2.0アプリ開発の基盤としてCとKotlinでだいたい同じ機能をもつMIDI APIを実装しているのだけど
C / Kotlin)

これに新機能をCでまず追加していた。テストも含めてそれなりの量のコードだ。

https://github.com/atsushieno/cmidi2/commit/c44d99a3aa1a198acaaa9c884706f49098f9701d

これをKotlinでも利用できるようにしたい。Cであれやこれや試行錯誤しながらコーディングしたものを移植するだけなので、がんばって肉体労働すれば難しくはないけど、やっぱり肉体労働は避けたい。

ならばある程度は自動化してしまおう。

Atsushi EnoAtsushi Eno

IDEAにはnj2kというJavaのソースコードをテキストエディタにペーストしたらKotlinに変換してくれる機能がある(コピペでなくてもメインメニューから変換を指示できるようにもなっている)。当然ながらJavaのコードにしか対応していないけど、KotlinはもともとJavaの土壌から生まれた言語ということもあって変換精度が非常に高い。

(JetBrains/Kotlinのリポジトリではnj2kというディレクトリに変換処理の実装がある。j2kディレクトリにあるものは古い。)

それであれば、いったんCのコードをJavaっぽいコードにして、そこからKotlinに変換してしまえば、退屈な作業のほとんどはnj2kにやらせてしまえば良いのではないか。そう思ってちょっと試しにやってみたら何とかなって、Cのコーディング作業でまる一日くらいかかっていた肉体労働が、1,2時間でだいたい何とかなった。

https://github.com/atsushieno/ktmidi/commit/ff81d4d08c28309824885ef893131671f32d02f4

Atsushi EnoAtsushi Eno

ただし当然ながらコピペする前のCのコードには変更を加えてあるし、ペーストした後のKotlinコードに修正が必要ないわけではない。あくまで人間にとって面倒な肉体労働(引数の順番の調整とか記号の調整とか)を省力化するためのものだ。自動変換にコストをかけすぎても時間の無駄だ。

いくつかTipsを書いておこう(記憶で書いているので完全なリストではない)。

構造体: これはrecord classにしてしまうのが良いし、nj2kを使わずに自前でコンストラクタに変換したほうが早い。次のようなstructがあるとする。

typedef struct xyz_tag {
    int a;
    float b;
} xyz;

メンバーの部分だけ選択して、エディタのfind/replaceで (.*) (.*);val \2 : \1に置き換える(型名は後でいじるので今はCのままで良い)。

    val a : int
    val b : float

頑張れば複数行選択でtypedef ... から型名まで自動でやってもいいと思うけど、今回はそこまで構造体宣言が無かったので手作業のほうが早かった。

ポインタ: これがあるとnj2kはJavaのコードだと認識しない。一番愚直なのは*が付いているやつをPtrとか適当な名前に変えてしまうことだけど、コレクションのとして使っているポインタなら、MutableList<T>にしてしまえば、foo[x] = y;スタイルのメンバーアクセスをそのまま残せるので、そうしたほうがよい。これがimmutableなList<T>だと、foo.get(x) = y みたいな謎コードが生成されて、結局エラーになってしまう。

void*をデータバッファとして使っているなら、MutableList<Byte>にしておくのが安牌だろう。

: これは、基本型については、だいたい次の通りで良いと思う。

  • char, unsigned char, uint8_t: UByteまたはByte
  • short, unsigned short, ushort, int16_t, uint16_t: UShortまたはShort
  • int32_t, uint32_t: UIntまたはInt
  • int, unsigned int: たぶんだいたいUIntまたはIntでよい
  • int64_t, uint64_t: ULongまたはLong
  • long unsigned long: たぶんだいたいULongまたはLongでよい

これは一定のベストプラクティスが無さそうで、プロジェクトに合わせて自分でアプローチを選択したほうが良いと思う。というのはCでカジュアルに使えるunsignedな整数がKotlinではカジュアルに使えない・experimentalTypesとして使えても型変換ですぐエラーになりやすい、という問題があるためだ。

今回のプロジェクトではさいわいほとんどsignedで扱える数値ばかりだったので(MIDI 1.0の仕組みがそうなっている)、Cライブラリで全部unsignedにしていたものが、Kotlinでは全部signedになった。

この変換はテキストエディタで雑に行えば良いと思う(といっても前後スペースくらいは付けておかないと想定外のテキストを変換する可能性があるのでフツーに気をつける)。

…こんなところだろうか。IDEAで変換対象のソースファイルをKotlinとして作って、エディタ上にペーストした時に「Javaのコードっぽいけど変換する?」って訊かれたらだいたい勝ちだ(前述のMutableListみたいに生成後のコードが無意味なものになっていたりすることもあるので、もう少し手を加えてから変換したほうが良い、みたいになることはある)。

Atsushi EnoAtsushi Eno

ちなみに単純にCのライブラリとして作った機能をKotlinでも使いたいという場合は、JavaCPPとか使ってJNI経由での呼び出しを自動生成したほうがメンテナンス性も高いだろう。

このライブラリはその意味では少々特殊で、Cライブラリはバインディングを作りやすいように出来ていない(リアルタイム処理を必要とするアプリに組み込みやすいようにsingle headerでstatic inlineだらけのライブラリにしてある)ので、Kotlin向けにだいたい同じ機能をもつAPIを実装している。