Successfully reported this slideshow.
Your SlideShare is downloading. ×

【20211202_toranoana.deno#3】denoでFFI

Advertisement

More Related Content

Slideshows for you

More from 虎の穴 開発室

Advertisement

Related Books

Free with a 30 day trial from Scribd

See all

【20211202_toranoana.deno#3】denoでFFI

  1. 1. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. denoでFFI
 
 虎の穴ラボ 藤原 佳顕
 1
  2. 2. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. アジェンダ
 ● 概要 ● やりたかったこと ● ソースを見てみよう ● 作ってみよう 2
  3. 3. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 自己紹介
 3 ● 名前
 ○ 藤原佳顕(@nuhera, @zonuko)
 ● 仕事
 ○ FantiaとかCreatiaとか社内アプリとか
 ● 好み
 ○ Clojure、Rust
 ● 趣味
 ○ 格闘ゲーム
 ■ Melty Blood、Guilty Gear
 ○ STG(ダライアスとか)も好き
 ○ 最近は女神転生Vずっとやってました

  4. 4. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 概要
 4 ● deno1.13あたりからFFI機能が追加されています 
 ○ what’s ffi?
 ■ 別の言語で作られた関すなどをまた別の言語から呼び出せる機能 
 ■ deno固有の言葉ではない
 ○ これによってDLLやSOファイルなど、いわゆるダイナミック/スタティックライブラリを Denoから呼び出せるように

  5. 5. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 概要
 5 ● 代表的なライブラリ
 ○ と言われて何が思い浮かびますか? 
 ○ libmysqlclient.so
 ○ RTP
 ■ RPGツクールのランタイムライブラリ群 
 ○ Win32 DLL (kernel32.dll、user32.dll、gdi32.dllなど) 

  6. 6. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. やりたかったこと
 ● DenoにFFIが入ったということはlibmysqlclient経由でmysql接続できるのでは?
 ● mysqlの接続方法自体はいくつかある
 ○ TCP接続
 ○ Unix Domain Socket接続
 ○ 名前付きパイプ
 ■ プロセス間通信で一般的に使われるやつ
 
 
 6
  7. 7. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. やりたかったこと
 ● とはいえDenoにはmysql用のライブラリがすでにある
 ○ https://deno.land/x/mysql@v2.10.1
 
 
 7 private async _connect() { // TODO: implement connect timeout const { hostname, port = 3306, socketPath, username = "", password } = this.config; log.info(`connecting ${this.remoteAddr}`); this.conn = !socketPath ? await Deno.connect({ transport: "tcp", hostname, port, }) : await Deno.connect({ transport: "unix", path: socketPath, } as any); … mysqlがインストールされているかに関わらず、tcp/ドメイ ンソケット経由なのがわかる
 

  8. 8. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. やりたかったこと
 
 ● ではDenoでのFFIするときにインターフェースはどうなっているか
 ○ ソースはこんな感じ
 
 
 
 
 
 ● 対応している型はchar / signed char, unsigned char, short int, unsigned short int, int / signed int, unsigned int, long long int, unsigned long long int, size_t, float, double, void, const uint8_t *
 ○ https://deno.land/manual@main/runtime/ffi_api
 
 8 const libName = `./libadd.${libSuffix}`; // Open library and define exported symbols const dylib = Deno.dlopen(libName, { "add": { parameters: ["isize", "isize"], result: "isize" }, });
  9. 9. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. やりたかったこと
 
 ● 一方でlibmysql側のインターフェースは? 
 ○ 例:コネクション貼りたい場合
 ■ https://dev.mysql.com/doc/refman/5.6/ja/mysql-real-connect.html 
 ■ MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag) ■ MYSQL構造体が出てきているので現段階ではうまく扱えなさそう 
 ● →とりあえず他の言語でどうやっているか見てみよう 
 9
  10. 10. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. ソースを見てみよう
 ● mysqlといえばRails
 ○ https://github.com/brianmario/mysql2
 ● Rubyには拡張ライブラリという仕組みがあります
 ○ https://docs.ruby-lang.org/en/2.4.0/extension_ja_rdoc.html
 ○ ルール基づいて作られたCライブラリをRuby側から一本のクラスやモジュールであるかのように呼び出せる
 ○ 抜粋
 ■ rb_define_module:新しいモジュール定義
 ■ rb_define_class_under: あるクラスを継承したクラスをモジュール配下に定義
 ■ rb_define_method:クラスにメソッドを定義
 ■ rb_define_private_method: クラスにプライベートメソッドを定義
 ○ Ruby側でどこからともなくconnectionメソッドが呼ばれているので、rb_define_method、rb_define_private_methodあたりで検 索かければなにかでてくるのでは?
 10
  11. 11. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. ソースを見てみよう
 ● https://github.com/brianmario/mysql2/blob/6652da20010ddfbbe6bceb8e41666d05e512346c/ext/mysql2/client.c#L445
 ● 最終的な部分
 11 static void *nogvl_connect(void *ptr) { struct nogvl_connect_args *args = ptr; MYSQL *client; client = mysql_real_connect(args->mysql, args->host, args->user, args->passwd, args->db, args->port, args->unix_socket, args->client_flag); return (void *)(client ? Qtrue : Qfalse); } =>直接libmysqlclient.soとかを呼び出していることはなく、C言語等でラップされている 

  12. 12. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 作ってみよう
 ● まずはコネクション取得(C側)
 ○ mysql_init→mysql_real_connectとする必要があるのでどちらのラッパー関数も定義(ついでにcloseも)
 12 #include <mysql/mysql.h> #include <stdio.h> MYSQL *conn = NULL; void init() { conn = mysql_init(NULL); } int connect(const char *host, const char *user, const char *passwd, const char *db_name, int port) { if (!mysql_real_connect(conn, host, user, passwd, db_name, port, NULL, 0)) { fprintf(stderr, "Failed to connect to database: Error: %sn", mysql_error(conn)); fflush(stderr); return 0; } return 1; } void close(void) { mysql_close(conn); }
  13. 13. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 作ってみよう
 ● ネクション取得(deno側)
 13 const C_TERMINAL_SYMBOL = "0"; const MYSQL_HOST = "localhost"; const MYSQL_USER = "root"; const MYSQL_PASSWD = ""; const MYSQL_DB_NAME = "mysql"; const MYSQL_PORT = 3306; function strToBytes(word: string): Uint8Array { return new TextEncoder().encode(`${word}${C_TERMINAL_SYMBOL}`); } function ffiSuffix(): string { switch (Deno.build.os) { case "windows": return "dll"; case "darwin": return "dylib"; case "linux": return "so"; } } interface LibInterfaces extends Record<string, Deno.ForeignFunction> { init: Deno.ForeignFunction; connect: Deno.ForeignFunction; close: Deno.ForeignFunction; } const libInterfaces: LibInterfaces = { init: { parameters: [], result: "void" }, connect: { parameters: ["buffer", "buffer", "buffer", "buffer", "i32"], result: "i32", }, close: { parameters: [], result: "void" }, };
  14. 14. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 作ってみよう
 ● コネクション取得続き(deno側)
 14 class MySQL { #dylib: Deno.DynamicLibrary<LibInterfaces> | null = null; readonly host = strToBytes(MYSQL_HOST); readonly user = strToBytes(MYSQL_USER); readonly passwd = strToBytes(MYSQL_PASSWD); readonly dbName = strToBytes(MYSQL_DB_NAME); private isConn = false; constructor() { const libName = `./ext/mysql/mysql.${ffiSuffix()}`; this.#dylib = Deno.dlopen(libName, libInterfaces); this.#dylib.symbols.init(); const res = this.#dylib.symbols.connect(this.host, this.user, this.passwd, this.dbName, MYSQL_PORT); if (res === 0) { this.close(); throw new Error("コネクション確立に失敗しました。 "); } this.isConn = true; } close() { if (!this.#dylib) { return; } if (this.isConn) { this.#dylib.symbols.close(); this.#dylib.close(); this.#dylib = null; } } } const client = new MySQL(); client.close();
  15. 15. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 作ってみよう
 ● 動かしてみる
 ○ libmysqlclient.(so|dll|dylib)のフォルダを指定してコンパイルする必要がある
 ○ 今回はコネクション貼って即時クローズするので、例外がでなければとりあえずOK
 15 > gcc -c -fPIC -o ./ext/mysql/mysql.o ./ext/mysql/mysql.c > gcc -shared -W -o ./ext/mysql/mysql.so ./ext/mysql/mysql.o -L/usr/lib/x86_64-linux-gnu -lmysqlclient > deno run --unstable --allow-ffi mod.ts (macの場合はlibフォルダが異なるので -L/usr/local/lib などとする)

  16. 16. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. 課題とハマったところ
 ● たまに接続文字列の後ろにゴミがくっついてエラーになる(“127.0.0.1L”とか)
 ○ bufferに文字列食わせるときに終端文字が必要だった(0)
 ○ C言語で文字列扱う場合は必要な処理だがDeno側に必要なのは盲点だった
 ● queryの結果取得が面倒
 ○ mysql_real_query→mysql_use_resultで結果取得といった流れ
 ○ mysql_use_resultの戻り値がMYSQL_RES構造体なのでこれもそのままは扱えない
 ○ また、まだbufferを戻り値にできない→クエリ実行結果を文字列としては戻せない
 ● そもそもRustでlibmysqlclientをラップしたほうが楽では?
 ○ こちらであればdeno_bindgenが使える
 ○ https://deno.land/manual@v1.16.3/runtime/ffi_api
 16
  17. 17. Copyright  (C) 2021 Toranoana Inc. All Rights Reserved. まとめ
 ● そのままでは複雑な構造体をI/Fとして持つものをFFIすることは難しそう 
 ● CやC++でさらに使いたいdllやsoファイルをラップする必要がありそう 
 ○ Cのグローバルにconnectionとか置かないとダメそう 
 ■ Cの中だけに収まってくれるか?という勝負 
 ● ただし、やり方さえ確立できればどんなものでもFFIできそう 
 ● より細かい話は12/4に公開するアドベントカレンダーのブログで紹介しています。 
 17

×