ffi.* API関数
このページでは、FFIライブラリによって提供されるAPI関数について詳細に説明します。まずは導入とFFIチュートリアルを読むことをお勧めします。
用語集
- cdecl — 抽象的なC型宣言(Lua文字列)。
- ctype — C型オブジェクト。これはffi.typeof()によって返される特別な種類のcdataです。呼び出されるとcdataコンストラクタとして機能します。
- cdata — Cデータオブジェクト。対応するctypeの値を保持します。
- ct — ほとんどのAPI関数で使用できるC型指定。cdecl、ctype、またはテンプレート型として機能するcdataのいずれか。
- cb — コールバックオブジェクト。特別な関数ポインタを保持するCデータオブジェクトです。この関数をCコードから呼び出すと、関連するLua関数が実行されます。
- VLA — 可変長配列は、要素数の代わりに?を使用して宣言されます。例えば"
int[?]
"。要素数(nelem)は作成時に指定する必要があります。 - VLS — 可変長構造体は、最後の要素がVLAである構造体C型です。宣言と作成に関する同じルールが適用されます。
外部シンボルの宣言とアクセス
外部シンボルは最初に宣言され、その後、特定のライブラリにシンボルを自動的にバインドするCライブラリ名前空間をインデックス化することでアクセスできます。
ffi.cdef(def)
型または外部シンボル(名前付き変数または関数)の複数のC宣言を追加します。defはLua文字列でなければなりません。文字列引数のシンタックスシュガーを以下のように使用することをお勧めします:
ffi.cdef[[
typedef struct foo { int a, b; } foo_t; // 構造体とtypedefを宣言。
int dofoo(foo_t *f, int n); /* 外部C関数を宣言。 */
]]
文字列の内容(上記の緑色の部分)は、セミコロンで区切られたC宣言のシーケンスでなければなりません。単一の宣言の場合、末尾のセミコロンは省略されることがあります。
外部シンボルは宣言されるだけで、まだ特定のアドレスにバインドされていないことに注意してください。バインドはCライブラリ名前空間(後述)を使用して行われます。
DANGER
Cの宣言はまだCプリプロセッサを通していません。#pragma packを除き、プリプロセッサトークンは許可されていません。既存のCヘッダーファイル内の#defineをenum、static const、typedefに置き換えるか、外部Cプリプロセッサを一度通してください。関連しないヘッダーファイルから不要または冗長な宣言を含めないように注意してください。
ffi.C
これはデフォルトのCライブラリ名前空間です — 大文字の'C'に注意してください。これはターゲットシステム上のデフォルトのシンボルまたはライブラリセットにバインドします。これらは、追加のリンクライブラリを指定せずに、Cコンパイラがデフォルトで提供するものとほぼ同じです。
POSIXシステムでは、これはデフォルトまたはグローバル名前空間内のシンボルにバインドします。これには、実行可能ファイルとグローバル名前空間にロードされた任意のライブラリからのすべてのエクスポートされたシンボルが含まれます。少なくともlibc、libm、libdl(Linux上)、libgcc(GCCでコンパイルされた場合)、およびLuaJIT自体によって提供されるLua/C APIからの任意のエクスポートされたシンボルが含まれます。
Windowsシステムでは、これは*.exe、lua51.dll(つまりLuaJIT自体によって提供されるLua/C API)、LuaJITがリンクされたCランタイムライブラリ(msvcrt*.dll)、kernel32.dll、user32.dll、gdi32.dllからエクスポートされたシンボルにバインドします。
clib = ffi.load(name [,global])
これはnameによって指定された動的ライブラリをロードし、そのシンボルにバインドする新しいCライブラリ名前空間を返します。POSIXシステムでは、globalがtrueの場合、ライブラリシンボルもグローバル名前空間にロードされます。
nameがパスの場合、ライブラリはこのパスからロードされます。そうでない場合、nameはシステム依存の方法で標準化され、動的ライブラリのデフォルトの検索パスで検索されます:
POSIXシステムでは、名前にドットが含まれていない場合、拡張子.soが追加されます。また、必要に応じてlib接頭辞が追加されます。したがって、ffi.load("z")はデフォルトの共有ライブラリ検索パスで"libz.so"を探します。
Windowsシステムでは、名前にドットが含まれていない場合、拡張子.dllが追加されます。したがって、ffi.load("ws2_32")はデフォルトのDLL検索パスで"ws2_32.dll"を探します。
cdataオブジェクトの作成
次のAPI関数はcdataオブジェクトを作成します(type()は"cdata"を返します)。作成されたすべてのcdataオブジェクトはガーベージコレクションされます。
cdata = ffi.new(ct [,nelem] [,init...])
cdata = ctype([nelem,] [init...])
指定されたctのcdataオブジェクトを作成します。VLA/VLS型はnelem引数が必要です。2番目の構文はctypeをコンストラクタとして使用し、それ以外は完全に等価です。
cdataオブジェクトは、オプションのinit引数を使用して、初期化子のルールに従って初期化されます。余分な初期化子はエラーを引き起こします。
パフォーマンスに関する注意:一種類のオブジェクトを多く作成したい場合は、cdeclを一度だけ解析し、そのctypeをffi.typeof()で取得してください。その後、ctypeを繰り返しコンストラクタとして使用します。
INFO
無名構造体の宣言は、ffi.new()で使用するたびに新しい、区別されたctypeを暗黙的に作成することに注意してください。これは特に、複数のcdataオブジェクトを作成する場合、望んでいることではないかもしれません。C標準では、異なる無名構造体はフィールドが同じでも代入互換とはみなされません!また、これらはJITコンパイラによって異なる型とみなされ、トレースの過剰な数を引き起こす可能性があります。名前付き構造体をffi.cdef()で宣言するか、無名構造体に対して単一のctypeオブジェクトをffi.typeof()で作成することを強く推奨します。
ctype = ffi.typeof(ct)
指定されたctのctypeオブジェクトを作成します。
この関数は、cdeclを一度だけ解析し、その結果得られたctypeオブジェクトをコンストラクタとして使用する場合に特に便利です。
cdata = ffi.cast(ct, init)
指定されたctのスカラーcdataオブジェクトを作成します。cdataオブジェクトはC型の変換ルールの「キャスト」バリアントを使用してinitで初期化されます。
この関数は主に、ポインターの互換性チェックをオーバーライドする場合や、ポインターをアドレスに変換する場合、またはその逆の場合に便利です。
ctype = ffi.metatype(ct, metatable)
指定されたctのctypeオブジェクトを作成し、それをメタテーブルと関連付けます。構造体/共用体型、複素数、ベクトルのみが許可されます。必要に応じて、他の型は構造体でラップできます。
メタテーブルとの関連付けは永続的であり、後から変更することはできません。メタテーブルの内容や__indexテーブル(存在する場合)の内容も後から変更することはできません。関連付けられたメタテーブルは、オブジェクトの作成方法や発生元に関係なく、この型のすべての使用に自動的に適用されます。型に対する事前定義された操作が優先されることに注意してください(例えば、宣言されたフィールド名はオーバーライドできません)。
すべての標準Luaメタメソッドが実装されています。これらは直接、ショートカットなしで、そして任意の型の組み合わせで呼び出されます。二項演算については、左オペランドが有効なctypeメタメソッドを持っているかどうかが最初にチェックされます。__gcメタメソッドは構造体/共用体型にのみ適用され、インスタンスの作成時に暗黙的なffi.gc()呼び出しを行います。
cdata = ffi.gc(cdata, finalizer)
ポインターまたは集合型cdataオブジェクトにファイナライザーを関連付けます。cdataオブジェクトは変更されずに返されます。
この関数は、管理されていないリソースをLuaJITガーベージコレクタの自動メモリ管理に安全に統合することを可能にします。典型的な使用法は次のとおりです:
local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
...
p = nil -- pへの最後の参照がなくなる。
-- GCは最終的にファイナライザーを実行します: ffi.C.free(p)
cdataファイナライザーはユーザーデータオブジェクトの__gcメタメソッドのように動作します:cdataオブジェクトへの最後の参照がなくなると、関連付けられたファイナライザーがcdataオブジェクトを引数として呼び出されます。ファイナライザーは、Lua関数またはcdata関数、またはcdata関数ポインターであることができます。既存のファイナライザーは、リソースを明示的に削除する直前にnilファイナライザーを設定することで削除できます:
ffi.C.free(ffi.gc(p, nil)) -- 手動でメモリを解放します。
C型の情報
次のAPI関数はC型に関する情報を返します。これらは主にcdataオブジェクトを検査するために役立ちます。
size = ffi.sizeof(ct [,nelem])
ctのサイズをバイト単位で返します。サイズが不明な場合(例えば"void"や関数型の場合)はnilを返します。VLA/VLS型の場合はnelemが必要ですが、cdataオブジェクトの場合は除きます。
align = ffi.alignof(ct)
ctの必要な最小アラインメントをバイト単位で返します。
ofs [,bpos,bsize] = ffi.offsetof(ct, field)
ctの開始からfieldまでのオフセット(バイト単位)を返します。ctは構造体でなければなりません。ビットフィールドの場合は、位置とフィールドサイズ(ビット単位)も返します。
status = ffi.istype(ct, obj)
objがctで指定されたC型を持っている場合はtrueを返します。それ以外の場合はfalseを返します。
C型の修飾子(constなど)は無視されます。ポインターは標準のポインター互換性ルールでチェックされますが、void *に対する特別な処理はありません。ctが構造体/共用体を指定している場合、この型へのポインターも受け入れられます。それ以外の場合、型は正確に一致する必要があります。
Note
この関数はobj引数に対してあらゆる種類のLuaオブジェクトを受け入れますが、非cdataオブジェクトに対しては常にfalseを返します。
ユーティリティ関数
err = ffi.errno([newerr])
最後にエラー状態を示したC関数呼び出しによって設定されたエラー番号を返します。オプションのnewerr引数が指定されている場合、エラー番号が新しい値に設定され、以前の値が返されます。
この関数は、エラー番号を取得および設定するためのポータブルでOSに依存しない方法を提供します。エラー番号を設定するC関数は一部に限られています。そして、それは関数が実際にエラー状態を示した場合(例えば-1またはNULLの戻り値で)のみ意味があります。そうでない場合、以前に設定された値を含んでいるかもしれませんし、含んでいないかもしれません。
関連するC関数の戻り値の直後に必要なとき、そして可能な限り早くこの関数を呼び出すことをお勧めします。errno値はフック、メモリ割り当て、JITコンパイラの呼び出し、その他の内部VMアクティビティを通じて保持されます。Windows上でGetLastError()によって返される値も同様ですが、それを宣言して自分自身で呼び出す必要があります。
str = ffi.string(ptr [,len])
ptrが指すデータからインターンされたLua文字列を作成します。
オプショナル引数lenが省略された場合、ptrは「char *」に変換され、データはゼロ終端であると見なされます。文字列の長さはstrlen()で計算されます。
それ以外の場合、ptrは「void *」に変換され、lenはデータの長さを示します。データには組み込みのゼロが含まれる場合があり、バイト指向である必要はありません(ただし、これはエンディアンの問題を引き起こす可能性があります)。
この関数は主に、C関数によって返された(一時的な)「const char *」ポインターをLua文字列に変換し、それを保存するか、Lua文字列を期待する他の関数に渡すために役立ちます。Lua文字列はデータの(インターンされた)コピーであり、もはや元のデータ領域とは関係がありません。Lua文字列は8ビットクリーンであり、任意の非文字データを保持するために使用できます。
パフォーマンスに関する注意:文字列の長さが分かっている場合は、長さを渡す方が速いです。例えば、sprintf()のようなCコールによって長さが返されるときなどです。
ffi.copy(dst, src, len)
ffi.copy(dst, str)
srcが指すデータをdstにコピーします。dstは「void *」に変換され、srcは「const void *」に変換されます。
最初の構文では、lenはコピーするバイト数を示します。注意:srcがLua文字列の場合、lenは#src+1を超えてはなりません。
二番目の構文では、コピーのソースはLua文字列でなければなりません。文字列の全バイトに加えてゼロ終端がdstにコピーされます(つまり、#src+1バイト)。
パフォーマンスに関する注意:ffi.copy()は、Cライブラリ関数memcpy()、strcpy()、strncpy()のより速い(インライン化可能な)代替として使用できます。
ffi.fill(dst, len [,c])
dstが指すデータを、cで指定されたlen個の定数バイトで埋めます。cが省略された場合、データはゼロで埋められます。
パフォーマンスに関する注意:ffi.fill()は、Cライブラリ関数memset(dst, c, len)のより速い(インライン化可能な)代替として使用できます。引数の順序が異なることに注意してください!
ターゲット固有の情報
status = ffi.abi(param)
param(Lua文字列)がターゲットのABI(アプリケーションバイナリインターフェース)に適用される場合はtrueを返します。そうでない場合はfalseを返します。現在定義されているパラメータは以下の通りです:
パラメータ | 説明 |
---|---|
32bit | 32ビットアーキテクチャ |
64bit | 64ビットアーキテクチャ |
le | リトルエンディアンアーキテクチャ |
be | ビッグエンディアンアーキテクチャ |
fpu | ハードウェアFPUを持つターゲット |
softfp | softfp呼び出し規約 |
hardfp | hardfp呼び出し規約 |
eabi | 標準ABIのEABIバリアント |
win | 標準ABIのWindowsバリアント |
pauth | ポインタ認証ABI |
uwp | ユニバーサルWindowsプラットフォーム |
gc64 | 64ビットGC参照 |
ffi.os
ターゲットOS名を含みます。jit.osと同じ内容です。
ffi.arch
ターゲットアーキテクチャ名を含みます。jit.archと同じ内容です。
コールバックのメソッド
コールバックのC型にはいくつかの追加メソッドがあります:
cb:free()
コールバックに関連付けられたリソースを解放します。関連するLua関数はアンカーされなくなり、ガベージコレクションされる可能性があります。コールバック関数ポインターはもはや有効ではなく、再び呼び出されるべきではありません(新しく作成されたコールバックによって再利用される可能性があります)。
cb:set(func)
コールバックに新しいLua関数を関連付けます。コールバックのC型とコールバック関数ポインターは変更されません。
このメソッドは、新しいコールバックを毎回作成して再登録することなく、コールバックの受信者を動的に切り替えるのに役立ちます(例:GUIライブラリで)。
標準ライブラリ関数の拡張
次の標準ライブラリ関数は、cdataオブジェクトで動作するように拡張されています:
n = tonumber(cdata)
数値cdataオブジェクトをダブルに変換し、それをLua数値として返します。これは特にボックス化された64ビット整数値にとって有用です。注意:この変換は精度の損失を招く可能性があります。
s = tostring(cdata)
64ビット整数("nnnLL"または"nnnULL")や複素数("re±imi")の値の文字列表現を返します。それ以外の場合は、ctypeオブジェクトのC型("ctype<type>
")またはcdataオブジェクト("cdata<type>: address
")の文字列表現を返します。ただし、__tostringメタメソッドでオーバーライドしない限り(ffi.metatype()を参照)。
iter, obj, start = pairs(cdata)
iter, obj, start = ipairs(cdata)
対応するctypeの__pairsまたは__ipairsメタメソッドを呼び出します。
Luaパーサーの拡張
Luaソースコードのパーサーは、接尾辞LLまたはULLを持つ数値リテラルを符号付きまたは符号なしの64ビット整数として扱います。大文字と小文字は区別されませんが、可読性のために大文字の使用が推奨されます。10進数(42LL)、16進数(0x2aLL)、2進数(0b101010LL)リテラルを処理します。
複素数の虚部は、数値リテラルにiまたはIを接尾辞として付けることで指定できます。例えば12.5iです。注意点として、i自体はまだ変数iを指しているので、値が1の虚部を得るには1iを使用する必要があります。