olofson / eel

The Extensible Embeddable Language for scripting in realtime applications
http://eelang.org/
zlib License
46 stars 4 forks source link

No way to pass data when implementing a class constructor in C #111

Closed chille closed 5 years ago

chille commented 5 years ago

There seems to be no way to pass data from the C function that registers a class with eel_export_class() to the constructor for the newly registered class.

I do the following:

I expect the following result:

I get the following result:

main.c:

#include <stdio.h>
#include <assert.h>
#include <EEL.h>

typedef struct
{
  int counter;
} D_moduledata;

static EEL_xno test_construct(EEL_vm *vm, EEL_types type, EEL_value *initv, int initc,
    EEL_value *result)
{
  D_moduledata *md = (D_moduledata *)eel_get_current_moduledata(vm);
  printf("Ptr: %p\n", md);
  assert(md != NULL);
}

int main(void)
{
  EEL_vm *vm = eel_open(0, NULL);

  D_moduledata *md = (D_moduledata *)eel_malloc(vm, sizeof(D_moduledata));
  printf("Ptr: %p\n", md);
  md->counter = 123;
  EEL_object *module = eel_create_module(vm, "testmodule", NULL, md);
  eel_export_class(module, "TestClass", EEL_COBJECT, test_construct, NULL, NULL);

  EEL_object *eelscript = eel_load(vm, "test.eel", 0);
  eel_callnf(vm, eelscript, "main", "");

  return 0;
}

test.eel:

export function main()<args>
{
  print("Hello world from EEL!\n");
  local temp = TestClass [];
  return 0;
}

Output:

# gcc main.c -I ../../eel/include/ ../../eel/src/core/libeel.so && ./a.out
Ptr: 0x564f55a2ae30
Hello world from EEL!
Ptr: (nil)
a.out: main.c:14: test_construct: Assertion md != NULL' failed.
olofson commented 5 years ago

This is a side effect of a design issue that hasn't been fixed yet. In short, eel_get_current_moduledata() only works from within functions - not constructors.

The problem is that a constructor is a just a raw C function from the EEL VM's perspective, so it doesn't have the 'function' EEL_object, where the common.module pointer would be found. Unfortunately, a class doesn't have any real ties to a module (apart from optionally being added to the 'exports' table of one), so the information is just not there.

However, there is a similar mechanism for this: 'classdata'. Each classdef has a void * field that can be set with eel_set_classdata() as the class is registered. It should be possible to retrieve it from within a constructor with eel_get_classdata() after you've created the object. (Technically, it only needs the VM and the type ID, but it grabs the latter from the object, as that's more convenient in most use cases.)

chille commented 5 years ago

I had a really hard time trying to figure out how to access the classdata from within the constructor. But just seconds before I was going to post this reply I found a new that worked out. However, it does not involve eel_get_classdata(). I cut 'n pasted the code from it, and changed a few things to get it to work.

static EEL_xno test_construct(EEL_vm *vm, EEL_types type, EEL_value *initv, int initc, EEL_value *result) {
  EEL_state *es = VMP->state;
  EEL_classdef *cd1 = o2EEL_classdef(es->classes[type]);
  ClassData *cd = (ClassData*)cd1->classdata;
}

Am I doing something wrong, is this a bug, or would it be a good idea to att a helper function or macro that does what I'm doing?

See the other non working examples: https://github.com/chille/eel/blob/classtest/examples/main2.c

EDIT: And BTW, the reason why I'm interested in the object orienting parts of EEL is that I already have an application written in C++. It would be nice to have an EEL API that is identical to my C++ classes. And as this is a realtime application EEL seems to be the best choice.

EDIT2: Removed the second question. I discovered it was just a bug in my code.

EDIT3: Another problem with the code above is that EEL_classdef contains a variable named typeid, which is a C++ operator, so e_class.h can not be included in a C++ project.

olofson commented 5 years ago

Indeed, looking up the classdata via the type ID is correct thing to do.

Using VMP is of course "internal stuff", and won't work against a normal install, as the e_* headers are not installed - and that's also why that field is still named 'typeid'. It's obviously not a great name, but that header is not supposed to be included by external code, so it shouldn't be a problem normally.

Anyway, eel_get_classdata() should probably just take a type ID instead of an object, so it can be used in this situation. No need to have both variants, and passing an object just to get the type ID from it is kind of silly.

On that note, that's probably the root of the problems you're having: You need to create an object of the class that the constructor is for, and pass that to eel_get_classdata(). A constructor is expected to do that (and set up "result" as a reference to that object), which is why eel_get_classdata() takes an object pointer in the first place; there will always be an object around when it's used. (Though one may well want to check some classdata stuff before deciding whether the create an instance, or fail...)

As for the variant using get_current_function(): That essentially has undefined behavior in this context! You need to pass an object of the class the constructor is for. get_current_function() returns a function, and since the 'function' class happens to have classdata (EEL_function_cd; internal VM stuff) you get a pointer to that, which is probably not what you want. :-)