I'm writing a language interpreter in C. I'm currently implementing a system that allows writing extension modules in C for the interpreter.
These modules are loaded into a code file like a normal module, but behind the scenes they are dynamically loaded libraries written in C. This is an approach similar to the one in the Python interpreter.
Inside C code (whether in the main interpreter or in an extension module), currently the way to instantiate a class is the following:
ObjectInstance* instance = vm_instantiate_class(class_object, arguments);
vm_instantiate_class
takes care of allocating the new instance, calling its classes' initializer method on it if present, and some more bookkeeping.
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;
}
This works great for creating instances, whether when called for user code in the main interpreter loop, and whether it's called from a C extension.
However, I'm running into a dilemma when considering how I should handle instantiating native classes.
Currently, a native class in an extension is implemented like so. We define a struct which "inherits" ObjectInstance
:
typedef struct {
ObjectInstance base;
// ... other native fields
int x;
int y;
} ObjectInstanceMyClass;
When object_instance_new
is called by vm_instantiate_class
as seen above, it checks on the class object if it's a native or user class. If it's a native one, the class object will say how much memory actually needs to be allocated for an instance (e.g. sizeof(ObjectInstanceMyClass)
). If it's a user class, sizeof(ObjectInstance)
is allocated.
This allows a native instance to be visible to user code like any other instance - but it can actually carry native data inside it, outside of the data types exposed through the language.
In an extension I'm writing, there is a native class that wraps a native OS resource - suppose a file handle. Inside the extension itself, I would like to be able to instantiate it the same elegant way I instantiate a regular class:
ValueArray args = make_value_array();
value_array_write(args, <a native C file handle>);
ObjectInstanceFile* file_object = (ObjectInstanceFile*) vm_instantiate_class(file_class, args);
However - I can't do that, since <a native C file handle>
isn't of a legal type in my language, and thus can't be written to a ValueArray
.
A workaround I can think of is this:
// 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
This approach would work - but it's just not elegant.
I would like to be able to call vm_instantiate_class
on a class which wraps a native resource (that has no equivalent in the language level), and have it work without having to further patch the instance after the call.
How is this sort of thing commonly implemented in language implementations? Specifically, I would like to know about the CPython implementation, but otherwise any language implementation example would do.