Pony 試乗記(5) C ライブラリの呼び出し

# はじめに

Pony をシミュレーションなどに利用するときのことを考えると GUI 描画ができると便利です。
Pony の Foreign Function Interface 機能の確認も兼ねて、freeglut ライブラリでの描画を行ってみます。

# Foreign Function Interface

Pony は [Foreign Function Interface](https://tutorial.ponylang.io/c-ffi/) を用いて C 言語の関数を呼び出すことができます。
コールバック関数なども Pony で記述して C 言語側から呼び出しさせることができます。

## ライブラリのリンク

使用するライブラリは、use "lib:〜" の形で指定します。

- libglut.so
- libGL.so
- libXmu.so
- libXi.so
- libXext.so
- libX11.so

をリンクしたければ

~~~pony
use "lib:glut"
use "lib:GL"
use "lib:Xmu"
use "lib:Xi"
use "lib:Xext"
use "lib:X11"
~~~

と書くだけです。なんとも簡単です。

ロードパスを設定する場合には、use "path:〜" で設定できます。

ソースコードで完結していて、別途設定ファイルを必要としないのが良いと思いました。

## 呼び出し先関数の定義

呼び出し先となるライブラリの関数

~~~c
extern void glutInitWindowSize( int width, int height );
~~~

が存在することを ponyc に伝えるために、

~~~pony
use @glutInitWindowSize[None](width: I32, height: I32)
~~~

と宣言します。これで Pony 側で `@glutInitWindowSize` が呼び出せるようになります。

もうすこし複雑なパターンとしてポインタを含む

~~~c
extern void glutInit( int* pargc, char** argv );
~~~

のようなものがあります。これは以下のように書けます。

~~~pony
use @glutInit[None](pargc: Pointer[I32] tag,
                    argv: Pointer[Pointer[U8] tag] tag)
~~~

構造体を渡す、とかになってくるともう少し複雑になってきそうですが、今回の freeglut を使う、という目的だと構造体は必要ありませんでした。

## 呼び出し

上で定義した <span>@</span>glutInitWindowSize のように I32 の数値を渡すだけなら、普通に Pony の関数を呼ぶのと同じように呼び出すことができます：

~~~pony
@glutInitWindowSize(512, 512)
~~~

`@glutInit` のようにポインタを含む場合には `addressof` で Pony の変数のアドレスを取得して渡します。`@glutInit` は Env の args を渡したいので、以下のようにしました。

~~~pony
actor Main
  new create(env: Env) =>
    // args のサイズを I32 型の数値にする。@glutInit に渡すのはこの変数のアドレス
    var argc: I32 = I32.from[USize](env.args.size())
    // args の内容を cstring （ポインタ）の配列に詰め直す
    let argv: Array[Pointer[U8 val] tag] = Array[Pointer[U8 val] tag]
    for arg in env.args.values() do
      argv.push(arg.cstring())
    end
    // @glutInit の呼び出し
    @glutInit(addressof argc, argv.cpointer())
~~~

## コールバック関数

### コールバック関数の定義

コールバック関数を引数とする C 関数

~~~c
extern void glutDisplayFunc( void (* callback)( void ) );
~~~

を呼び出せるように、Pony 上で以下のように宣言します:

~~~pony
use @glutDisplayFunc[None](callback: Pointer[None])
~~~

コールバック関数の型は `None` へのポインタ（`Pointer[None]` ー C での void ポインタに相当する）としておきます。

`@glutDisplayFunc` に渡すコールバック関数を

~~~pony
class Display
  fun @callback(): None =>
    (略)
~~~

のように書いておけば、`@glutDisplayFunc` 呼び出しは以下のように書けます（引数として渡すコールバック関数に `addressof` をつけるのがみそ。C++ で `&Display::callback` のようにするのと似たようなものか）。

~~~pony
  @glutDisplayFunc(addressof Display.callback)
~~~

# 感想

実際に書いてみるとドキュメントを読んで想像していた以上に簡単に書けて驚きました。

Pony の変数のアドレスを `addressof` で取得して FFI に渡せる、というのは Pony が GC 時にコンパクションをしていないという事実の現れです。また、FFI に I32 や I64 といった型の数値をそのまま渡せるのを見ると Pony 内部でタグ付きの値だとか凝った内部表現を使っていないのだなあと実感できました。

C 言語の関数の呼び出しは C 言語実装部分がメモリ破壊を引き起こす可能性もあります。かといって、メモリ破壊の影響を防ぐために Pony 実装プログラムは C 実装プログラムと別プロセスで立ち上げて通信で協調動作させるというのも面倒です。

比較的信頼できる既存ライブラリを呼び出したい、というときには FFI でよさそうです。

# ソースコード

~~~pony
/* ---------------------------------------------------------------------- *
 * Pony の FFI を利用して freeglut による描画を行うテスト
 * ---------------------------------------------------------------------- */
// 依存ライブラリ
// libglut.so
// libGL.so
// libXmu.so
// libXi.so
// libXext.so
// libX11.so
use "lib:glut"
use "lib:GL"
use "lib:Xmu"
use "lib:Xi"
use "lib:Xext"
use "lib:X11"

/* ---------------------------------------------------------------------- *
 * freeglut                                                               *
 * ---------------------------------------------------------------------- */
/*
Initialization functions
*/
// FGAPI void    FGAPIENTRY glutInit( int* pargc, char** argv );
use @glutInit[None](pargc: Pointer[I32] tag,
                    argv: Pointer[Pointer[U8] tag] tag)
// FGAPI void    FGAPIENTRY glutInitWindowSize( int width, int height );
use @glutInitWindowSize[None](width: I32, height: I32)
// FGAPI void    FGAPIENTRY glutInitDisplayMode( unsigned int displayMode );
use @glutInitDisplayMode[None](displayMode: U32)
/*
Window management functions
*/
// FGAPI int     FGAPIENTRY glutCreateWindow( const char* title );
use @glutCreateWindow[I32](title: Pointer[U8] tag)

/*
Display-connected functions
*/
// FGAPI void    FGAPIENTRY glutSwapBuffers( void );
use @glutSwapBuffers[None]()

/*
Window-specific callback functions, see freeglut_callbacks.c
*/
// FGAPI void    FGAPIENTRY glutDisplayFunc( void (* callback)( void ) );
use @glutDisplayFunc[None](callback: Pointer[None])
/*
Process loop function
*/
// FGAPI void    FGAPIENTRY glutMainLoop( void );
use @glutMainLoop[None]()

/* ---------------------------------------------------------------------- *
 * GL                                                                     *
 * ---------------------------------------------------------------------- */

/*
 * Miscellaneous
 */
// GLAPI void GLAPIENTRY glClear( GLbitfield mask );
use @glClear[None](mask: U32)
// GLAPI void GLAPIENTRY glFlush( void );
use @glFlush[None]()

/*
 * Transformation
 */
// GLAPI void GLAPIENTRY glOrtho( GLdouble left, GLdouble right,
//                                GLdouble bottom, GLdouble top,
//                                GLdouble near_val, GLdouble far_val );
use @glOrtho[None](left: F64, right: F64,
                   bottom: F64, top: F64,
                   near_val: F64, far_val: F64)

/*
 * Drawing Functions
 */
// GLAPI void GLAPIENTRY glBegin( GLenum mode );
use @glBegin[None](mode: U32)
// GLAPI void GLAPIENTRY glEnd( void );
use @glEnd[None]()
// GLAPI void GLAPIENTRY glVertex2i( GLint x, GLint y );
use @glVertex2i[None](x: I32, y: I32)
// GLAPI void GLAPIENTRY glColor3f( GLfloat red, GLfloat green, GLfloat blue );
use @glColor3f[None](red: F32, gree: F32, blue: F32)

/*
 * freeglut の enum 値
 */
primitive GLUT
  fun rgb(): U32 => 0x0000
  // #define  GLUT_RGBA                          0x0000
  fun rgba(): U32 => 0x0000

/*
 * GL の enum 値
 */
primitive GL
  // #define GL_QUADS                                0x0007
  fun quads(): U32 => 0x0007
  // #define GL_DEPTH_BUFFER_BIT                     0x00000100
  fun depth_buffer_bit(): U32 => 0x00000100
  // #define GL_COLOR_BUFFER_BIT                     0x00004000
  fun color_buffer_bit(): U32 => 0x00004000

// ----------------------------------------------------------------------

class Display
  """
  glutDisplayFunc から呼び出されるコールバック関数を定義するクラス
  """
  fun @callback(): None =>
    @glClear(GL.color_buffer_bit() or GL.depth_buffer_bit())
    var x: I32 = 0
    while x < 512 do
      x = x + 1
      var y: I32 = 0
      while y < 512 do
        @glColor3f(1.0, F32.from[I32](x) / 512.0, F32.from[I32](y) / 512.0)
        @glBegin(GL.quads())
        @glVertex2i(x, y)
        @glVertex2i(x + 1, y)
        @glVertex2i(x + 1, y + 1)
        @glVertex2i(x, y + 1)
        @glEnd()
        y = y + 1
      end
    end
    @glutSwapBuffers()
    @glFlush()

actor Main
  new create(env: Env) =>
    try
      var argc: I32 = I32.from[USize](env.args.size())
      let argv: Array[Pointer[U8 val] tag] = Array[Pointer[U8 val] tag]
      for arg in env.args.values() do
        argv.push(arg.cstring())
      end
      @glutInitWindowSize(512, 512)
      @glutInit(addressof argc, argv.cpointer())
      @glutInitDisplayMode(GLUT.rgb())
      @glutCreateWindow(env.args(0)?.cstring())
      @glutDisplayFunc(addressof Display.callback)
      @glOrtho(0, 512, 512, 0, -1, 1)
      @glutMainLoop()
    else
      env.out.print("internal error")
    end
~~~