私はCで言語インタープリターを作成しています。現在、インタープリター用のCで拡張モジュールを作成できるシステムを実装しています。
これらのモジュールは通常のモジュールと同じようにコードファイルに読み込まれますが、舞台裏ではCで書かれた動的に読み込まれたライブラリです。これは、Pythonインタープリターの場合と同様のアプローチです。
Cコード内(メインインタープリター内または拡張モジュール内)で、現在クラスをインスタンス化する方法は次のとおりです。
_ObjectInstance* instance = vm_instantiate_class(class_object, arguments);
_
_vm_instantiate_class
_は、新しいインスタンスの割り当て、存在する場合はそのインスタンスのクラスの初期化メソッドを呼び出し、さらにいくつかの簿記を行います。
_ObjectInstance* vm_instantiate_class(ObjectClass* klass, ValueArray args) {
// Allocate instance
ObjectInstance* instance = object_instance_new(klass);
// This goes to the instance's class, gets the requested attribute, and if it's a method,
// the method is wrapped with a BoundMethod so it remembers it's associated instance when it's called
ObjectBoundMethod* init_method = (ObjectBoundMethod*) object_load_attribute((Object*) instance, "@init");
// Invoke the initializer on the instance
vm_call_bound_method(init_method, args);
instance->is_initialized = true;
return instance;
}
_
これは、メインインタープリターループでユーザーコードを呼び出す場合や、C拡張から呼び出される場合など、インスタンスの作成に最適です。
ただし、ネイティブクラスのインスタンス化を処理する方法を検討しているときに、ジレンマに直面しています。
現在、拡張機能のネイティブクラスはそのように実装されています。 「継承」する構造体を定義しますObjectInstance
:
_typedef struct {
ObjectInstance base;
// ... other native fields
int x;
int y;
} ObjectInstanceMyClass;
_
上記のように_object_instance_new
_が_vm_instantiate_class
_によって呼び出されると、クラスオブジェクトがネイティブクラスかユーザークラスかを確認します。それがネイティブのものである場合、クラスオブジェクトは、インスタンスに実際に割り当てる必要があるメモリの量を示します(例:sizeof(ObjectInstanceMyClass)
)。ユーザークラスの場合、sizeof(ObjectInstance)
が割り当てられます。
これにより、ネイティブインスタンスを他のインスタンスと同様にユーザーコードで表示できるようになりますが、実際には、言語を介して公開されるデータ型の外で、ネイティブインスタンスをその内部に保持できます。
私が書いている拡張機能には、ネイティブOSリソースをラップするネイティブクラスがあります-ファイルハンドルを想定します。拡張機能自体の内部で、通常のクラスをインスタンス化するのと同じエレガントな方法でインスタンス化できるようにしたいと思います。
_ValueArray args = make_value_array();
value_array_write(args, <a native C file handle>);
ObjectInstanceFile* file_object = (ObjectInstanceFile*) vm_instantiate_class(file_class, args);
_
ただし、_<a native C file handle>
_は私の言語では有効なタイプではないため、ValueArray
に書き込むことができないため、これを行うことはできません。
私が考えることができる回避策はこれです:
_// Use vm_instantiate_class to create an instance, and do actual initialization
// ad-hoc in the outside code.
ValueArray args = make_value_array(); // Empty args
ObjectInstance* instance = vm_instantiate_class(file_class, args);
ObjectInstanceFile* file_object = (ObjectInstanceFile*) instance;
file_object->handle = <a native C file handle>; // Initialize the field
_
このアプローチは機能しますが、エレガントではありません。
(言語レベルで同等のものがない)ネイティブリソースをラップするクラスで_vm_instantiate_class
_を呼び出して、呼び出し後にインスタンスにさらにパッチを適用しなくても機能できるようにしたいと考えています。
この種のことは、言語実装でどのように一般的に実装されていますか?具体的には、CPythonの実装について知りたいのですが、それ以外の言語の実装例ではそれが可能です。
CPythonについてはわかりませんが、Luaインタープリター(および親戚)によって行われる解決策は、_void *
_ +メソッドに対応するオブジェクト型を用意することです(Luaはこのユーザーデータを呼び出します)。 Luaにはコンストラクターがないため、ラッパーを効果的に返すmake_userdata(void *, methods)
関数だけが必要です。これは、args
から_vm_instantiate_class
_に、そのようなユーザーデータを1つ渡すことで簡単に適応できます(メソッドをサポートする必要すらなく、文字通りポインターのラッパーにすぎません)。
一方、コンストラクター(_@init
_)があります。代わりに、ハンドルをコンストラクタで開かないのはなぜですか?その後、望みどおりにセットアップできます。すべてがうまくカプセル化されており、おまけとして、オブジェクトは実際にはユーザーコードからインスタンス化できますが、現在のシステムではそうではないようです。