diff --git a/source/configuration/source/configuration_object.c b/source/configuration/source/configuration_object.c index e49e8a733d..8d5ec45848 100644 --- a/source/configuration/source/configuration_object.c +++ b/source/configuration/source/configuration_object.c @@ -104,8 +104,6 @@ configuration configuration_object_initialize(const char *name, const char *path if (config->source == NULL) { - log_write("metacall", LOG_LEVEL_ERROR, "Failed to load configuration %s from %s", name, path); - free(config); return NULL; @@ -268,6 +266,8 @@ int configuration_object_childs(configuration config, vector childs, set storage if (child == NULL) { + log_write("metacall", LOG_LEVEL_ERROR, "Failed to load configuration %s from %s", (char *)key, path); + return 1; } diff --git a/source/loaders/c_loader/source/c_loader_impl.cpp b/source/loaders/c_loader/source/c_loader_impl.cpp index a4e57914c4..bf6a6bd0bf 100644 --- a/source/loaders/c_loader/source/c_loader_impl.cpp +++ b/source/loaders/c_loader/source/c_loader_impl.cpp @@ -516,6 +516,88 @@ class c_loader_closure_value } }; +class c_loader_pointer_type : public c_loader_type_impl +{ +protected: + CXType cx_type; + +public: + c_loader_pointer_type(CXType cx_type) : + cx_type(cx_type) {} + + void *to_value(void *arg_ptr) + { + // TODO: This may be too tricky to implement because it is impossible to reconstruct the pointer from the type + // easily, as C does not mantain the true memory layout, pointers can be arrays or single elements and we cannot know + // We should review this carefully + (void)arg_ptr; +#if 0 + CXType type_iterator = cx_type; + value prev_value = value_create_ptr(NULL); + void *current_ptr = arg_ptr; + + while (type_iterator.kind == CXType_Pointer) + { + value new_value = value_create_ptr(NULL); + + if (prev_value != NULL) + { + value_from_ptr(prev_value, ) + } + + type_iterator = clang_getPointeeType(type_iterator); + prev_ptr = current_ptr; + } + + std::vector type_info; + std::vector ptr_info; + void *arg_it_ptr = arg_ptr; + void *result = NULL; + + while (type_iterator.kind == CXType_Pointer) + { + type_info.push_back(type_iterator.kind); + ptr_info.push_back(arg_it_ptr); + type_iterator = clang_getPointeeType(type_iterator); + arg_it_ptr = *(static_cast(arg_it_ptr)); + } + + type_info.push_back(type_iterator.kind); + ptr_info.push_back(arg_it_ptr); // TODO: is this safe? + + for (auto type_iterator = type_info.rbegin(); type_iterator != type_info.rend(); ++type_iterator) + { + switch (*type_iterator) + { + case CXType_Pointer: + { + break; + } + + case CXType_String: + { + break; + } + + #if 0 + if (pointee_type.kind == CXType_Char_S || pointee_type.kind == CXType_SChar || + pointee_type.kind == CXType_Char_U || pointee_type.kind == CXType_UChar) + { + return TYPE_STRING; + } + /* Support for function pointers */ + else if (pointee_type.kind == CXType_FunctionProto || pointee_type.kind == CXType_FunctionNoProto) + #endif + } + } +#endif + + return NULL; + } + + ~c_loader_pointer_type() {} +}; + std::string c_loader_impl_cxstring_to_str(const CXString &s) { std::string result = clang_getCString(s); @@ -633,10 +715,14 @@ ffi_type *c_loader_impl_ffi_type(type_id id) return &ffi_type_float; case TYPE_DOUBLE: return &ffi_type_double; + case TYPE_STRING: + return &ffi_type_pointer; case TYPE_PTR: return &ffi_type_pointer; case TYPE_FUNCTION: return &ffi_type_pointer; + case TYPE_NULL: + return &ffi_type_void; } return &ffi_type_void; @@ -658,6 +744,7 @@ function_return function_c_interface_invoke(function func, function_impl impl, f for (size_t args_count = 0; args_count < args_size; ++args_count) { type t = signature_get_type(s, args_count); + /* type_impl impl_type = type_derived(t); */ type_id id = type_index(t); type_id value_id = value_type_id((value)args[args_count]); @@ -683,8 +770,25 @@ function_return function_c_interface_invoke(function func, function_impl impl, f closures.push_back(closure); } +#if 0 + else if (id == TYPE_STRING || (id == TYPE_PTR && impl_type != nullptr)) +#endif + else if (id == TYPE_STRING) + { + /* String requires to be pointer to a string and + Pointer requires to be pointer to pointer */ + c_function->values[args_count] = value_create_ptr((value)args[args_count]); + } else { + /* In case of (id == TYPE_PTR && impl_type == nullptr it means that + the parameter is normally int*, long*, etc... we just let the pointer + be passed as it is because it cannot output a pointer, only modify the contents + */ + + /* TODO: Check if types are not equal and do a casting? + Note: It will involve destroying the new casted values */ + c_function->values[args_count] = value_data((value)args[args_count]); } } @@ -693,9 +797,7 @@ function_return function_c_interface_invoke(function func, function_impl impl, f size_t ret_size = value_type_id_size(ret_id); void *ret = NULL; - /* TODO: This if is not correct because the sizes of strings, objects, etc are - relative to the pointer, not the value contents, we should review this */ - if (ret_size <= sizeof(ffi_arg)) + if (ret_size <= sizeof(ffi_arg) && ret_id < TYPE_STRING) { ffi_arg result; @@ -705,9 +807,71 @@ function_return function_c_interface_invoke(function func, function_impl impl, f } else { - ret = value_type_create(NULL, ret_size, ret_id); + void *result = NULL; + void *result_ptr = &result; - ffi_call(&c_function->cif, FFI_FN(c_function->address), value_data(ret), c_function->values); + if (ret_id == TYPE_NULL) + { + ret = value_create_null(); + result_ptr = NULL; + } + else if (ret_id != TYPE_STRING && ret_id != TYPE_PTR) + { + result = ret = value_type_create(NULL, ret_size, ret_id); + } + + ffi_call(&c_function->cif, FFI_FN(c_function->address), result_ptr, c_function->values); + + if (ret_id == TYPE_STRING) + { + char *str = (char *)result; + ret = value_create_string(str, strlen(str)); + } + else if (ret_id == TYPE_PTR) + { + ret = value_create_ptr(result); + } + } + + for (size_t args_count = 0; args_count < args_size; ++args_count) + { + type t = signature_get_type(s, args_count); + /* type_impl impl_type = type_derived(t); */ + type_id id = type_index(t); + + /* This is very tricky, if the type was a pointer to pointer, if it + replaced the target pointer we have to update it in the MetaCall value, + for example: + void f(char **str_ptr) + { + *str_ptr = "aaa"; + } + In order to make it work we have to recreate the metacall value, this is + highly unsafe if we mix types because we will use the type info of the + underlaying type in order to recreate it, in this example, a string + */ +#if 0 + if (id == TYPE_PTR && impl_type != nullptr) + { + // TODO: This may be too tricky to implement because it is impossible to reconstruct the pointer from the type + // easily, as C does not mantain the true memory layout, pointers can be arrays or single elements and we cannot know + // We should review this carefully + #if 0 + /* Reconstruct the pointer value from the type info */ + c_loader_pointer_type *pointer_type = static_cast(impl_type); + + void *arg_value = pointer_type->to_value(value_to_ptr(c_function->values[args_count])); + #endif + + value_type_destroy(c_function->values[args_count]); + } + else +#endif + if (id == TYPE_STRING) + { + /* Clear the pointer to string allocated before */ + value_type_destroy(c_function->values[args_count]); + } } /* Clear allocated closures if any */ @@ -800,6 +964,7 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_BOOL, "bool" }, { TYPE_CHAR, "char" }, + { TYPE_CHAR, "unsigned char" }, { TYPE_CHAR, "int8_t" }, { TYPE_CHAR, "uint8_t" }, { TYPE_CHAR, "int_least8_t" }, @@ -808,6 +973,7 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_CHAR, "uint_fast8_t" }, { TYPE_SHORT, "short" }, + { TYPE_SHORT, "unsigned short" }, { TYPE_SHORT, "int16_t" }, { TYPE_SHORT, "uint16_t" }, { TYPE_SHORT, "int_least16_t" }, @@ -816,6 +982,7 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_SHORT, "uint_fast16_t" }, { TYPE_INT, "int" }, + { TYPE_INT, "unsigned int" }, { TYPE_INT, "uint32_t" }, { TYPE_INT, "int32_t" }, { TYPE_INT, "int_least32_t" }, @@ -824,7 +991,9 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_INT, "uint_fast32_t" }, { TYPE_LONG, "long" }, + { TYPE_LONG, "unsigned long" }, { TYPE_LONG, "long long" }, + { TYPE_LONG, "unsigned long long" }, { TYPE_LONG, "uint64_t" }, { TYPE_LONG, "int64_t" }, { TYPE_LONG, "int_least64_t" }, @@ -838,6 +1007,11 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_FLOAT, "float" }, { TYPE_DOUBLE, "double" }, + { TYPE_STRING, "unsigned char *" }, + { TYPE_STRING, "char *" }, + { TYPE_STRING, "const unsigned char *" }, + { TYPE_STRING, "const char *" }, + { TYPE_NULL, "void" } /* TODO: Do more types */ @@ -928,6 +1102,13 @@ static type_id c_loader_impl_clang_type(loader_impl impl, CXCursor cursor, CXTyp { return c_loader_impl_clang_type(impl, cursor, pointee_type, impl_type); } + /* Check for pointers to pointers, in this case we need the type info for reconstructing the data */ + else if (pointee_type.kind == CXType_Pointer) + { + c_loader_pointer_type *pointer_type = new c_loader_pointer_type(cx_type); + + *impl_type = static_cast(pointer_type); + } return TYPE_PTR; } @@ -964,7 +1145,12 @@ static type_id c_loader_impl_clang_type(loader_impl impl, CXCursor cursor, CXTyp case CXType_Bool: return TYPE_BOOL; + case CXType_Short: + case CXType_UShort: + return TYPE_SHORT; + case CXType_Int: + case CXType_UInt: return TYPE_INT; case CXType_Void: diff --git a/source/loaders/node_loader/source/node_loader_impl.cpp b/source/loaders/node_loader/source/node_loader_impl.cpp index e12594884a..b493d19646 100644 --- a/source/loaders/node_loader/source/node_loader_impl.cpp +++ b/source/loaders/node_loader/source/node_loader_impl.cpp @@ -1442,14 +1442,13 @@ value node_loader_impl_napi_to_value(loader_impl_node node_impl, napi_env env, n } else if (valuetype == napi_external) { - /* Returns the previously allocated copy */ void *c = nullptr; status = napi_get_value_external(env, v, &c); node_loader_impl_exception(env, status); - return c; + return value_create_ptr(c); } return ret; @@ -1635,10 +1634,7 @@ napi_value node_loader_impl_value_to_napi(loader_impl_node node_impl, napi_env e } else if (id == TYPE_PTR) { - /* Copy value and set the ownership, the old value will be deleted after the call */ - void *c = value_copy(arg_value); - - value_move(arg_value, c); + void *c = value_to_ptr(arg_value); status = napi_create_external(env, c, nullptr, nullptr, &v); diff --git a/source/loaders/node_loader/source/node_loader_port.cpp b/source/loaders/node_loader/source/node_loader_port.cpp index 82eaafbff9..720572f956 100644 --- a/source/loaders/node_loader/source/node_loader_port.cpp +++ b/source/loaders/node_loader/source/node_loader_port.cpp @@ -34,9 +34,12 @@ #include #include +#include + #include static const loader_tag node_loader_tag = "node"; +static std::set metacall_value_reference_pointers; napi_value node_loader_port_metacall(napi_env env, napi_callback_info info) { @@ -1044,7 +1047,6 @@ napi_value node_loader_port_metacall_inspect(napi_env env, napi_callback_info) return result; } -/* TODO: Add documentation */ napi_value node_loader_port_metacall_logs(napi_env env, napi_callback_info) { struct metacall_log_stdio_type log_stdio = { stdout }; @@ -1057,6 +1059,172 @@ napi_value node_loader_port_metacall_logs(napi_env env, napi_callback_info) return nullptr; } +/* TODO: Add documentation */ +napi_value node_loader_port_metacall_value_create_ptr(napi_env env, napi_callback_info info) +{ + const size_t args_size = 1; + size_t argc = args_size; + napi_value argv[args_size]; + void *v; + + // Get arguments + napi_status status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + node_loader_impl_exception(env, status); + + if (argc == 0) + { + v = NULL; + } + else if (argc == 1) + { + napi_valuetype arg_type; + + status = napi_typeof(env, argv[0], &arg_type); + + node_loader_impl_exception(env, status); + + if (arg_type == napi_undefined) + { + v = NULL; + } + else if (arg_type == napi_external) + { + // Copy the external pointer + status = napi_get_value_external(env, argv[0], &v); + + node_loader_impl_exception(env, status); + } + else + { + napi_throw_type_error(env, nullptr, "Invalid MetaCall value create pointer, you need to pass undefined or external as a parameter"); + } + } + else + { + napi_throw_error(env, nullptr, "Invalid MetaCall value create pointer, you need to pass 0 or 1 parameters"); + } + + napi_value result_external; + + status = napi_create_external(env, &v, NULL, NULL, &result_external); + + node_loader_impl_exception(env, status); + + return result_external; +} + +static void metacall_value_reference_finalize(napi_env env, void *finalize_data, void *finalize_hint) +{ + value v = finalize_data; + (void)env; + (void)finalize_hint; + metacall_value_destroy(v); + metacall_value_reference_pointers.erase(v); +} + +/* TODO: Add documentation */ +napi_value node_loader_port_metacall_value_reference(napi_env env, napi_callback_info info) +{ + const size_t args_size = 1; + size_t argc = args_size; + napi_value recv; + napi_value argv[args_size]; + value v; + + // Get arguments + napi_status status = napi_get_cb_info(env, info, &argc, argv, &recv, nullptr); + + node_loader_impl_exception(env, status); + + if (argc != 1) + { + napi_throw_type_error(env, NULL, "Invalid number of arguments, use it like: metacall_value_reference(obj);"); + return nullptr; + } + + /* Obtain NodeJS loader implementation */ + loader_impl impl = loader_get_impl(node_loader_tag); + loader_impl_node node_impl = (loader_impl_node)loader_impl_get(impl); + + /* Store current reference of the environment */ + node_loader_impl_env(node_impl, env); + + v = node_loader_impl_napi_to_value(node_impl, env, recv, argv[0]); + + if (v == NULL) + { + napi_throw_error(env, NULL, "Failed to convert the JavaScript object to MetaCall value."); + return nullptr; + } + + napi_value result_external; + + status = napi_create_external(env, v, &metacall_value_reference_finalize, nullptr, &result_external); + + node_loader_impl_exception(env, status); + + metacall_value_reference_pointers.insert(v); + + return result_external; +} + +/* TODO: Add documentation */ +napi_value node_loader_port_metacall_value_dereference(napi_env env, napi_callback_info info) +{ + const size_t args_size = 1; + size_t argc = args_size; + napi_value recv; + napi_value argv[args_size]; + value v; + + // Get arguments + napi_status status = napi_get_cb_info(env, info, &argc, argv, &recv, nullptr); + + node_loader_impl_exception(env, status); + + if (argc != 1) + { + napi_throw_type_error(env, NULL, "Invalid number of arguments, use it like: metacall_value_dereference(ptr);"); + return nullptr; + } + + napi_valuetype type; + + status = napi_typeof(env, argv[0], &type); + node_loader_impl_exception(env, status); + + if (type != napi_external) + { + napi_throw_type_error(env, NULL, "Invalid parameter type in first argument must be a PyCapsule (i.e a previously allocated pointer)"); + return NULL; + } + + // Get the external pointer + status = napi_get_value_external(env, argv[0], &v); + + node_loader_impl_exception(env, status); + + // If it is not contained in the set, it is not a valid value + if (metacall_value_reference_pointers.find(v) == metacall_value_reference_pointers.end()) + { + napi_throw_type_error(env, NULL, "Invalid reference, argument must be a PyCapsule containing a MetaCall value, use it only with values returned by metacall_value_reference"); + return NULL; + } + + /* Obtain NodeJS loader implementation */ + loader_impl impl = loader_get_impl(node_loader_tag); + loader_impl_node node_impl = (loader_impl_node)loader_impl_get(impl); + + /* Store current reference of the environment */ + node_loader_impl_env(node_impl, env); + + /* Get the N-API value */ + napi_value result = node_loader_impl_value_to_napi(node_impl, env, v); + + return result; +} + napi_value node_loader_port_register_bootstrap_startup(napi_env env, napi_callback_info) { /* Obtain NodeJS loader implementation */ @@ -1097,6 +1265,9 @@ void node_loader_port_exports(napi_env env, napi_value exports) x(metacall_load_from_configuration); \ x(metacall_load_from_configuration_export); \ x(metacall_inspect); \ + x(metacall_value_create_ptr); \ + x(metacall_value_reference); \ + x(metacall_value_dereference); \ x(metacall_logs); \ x(register_bootstrap_startup); diff --git a/source/loaders/py_loader/source/py_loader_port.c b/source/loaders/py_loader/source/py_loader_port.c index 8f60578e3b..b9da55d913 100644 --- a/source/loaders/py_loader/source/py_loader_port.c +++ b/source/loaders/py_loader/source/py_loader_port.c @@ -846,7 +846,7 @@ static PyObject *py_loader_port_value_dereference(PyObject *self, PyObject *args if (name != py_loader_capsule_reference_id) { - PyErr_SetString(PyExc_TypeErrorPtr(), "Invalid reference, argument must be a PyCapsule from MetaCall"); + PyErr_SetString(PyExc_TypeErrorPtr(), "Invalid reference, argument must be a PyCapsule containing a MetaCall value, use it only with values returned by metacall_value_reference"); return Py_ReturnNone(); } diff --git a/source/metacall/source/metacall.c b/source/metacall/source/metacall.c index cd73661845..8b9acaa74a 100644 --- a/source/metacall/source/metacall.c +++ b/source/metacall/source/metacall.c @@ -1119,8 +1119,15 @@ void *metacallfv_s(void *func, void *args[], size_t size) { if (value_validate(args[iterator]) != 0) { + const char *name = function_name(f); + + if (name == NULL) + { + name = "anonymous"; + } + // TODO: Implement type error return a value - log_write("metacall", LOG_LEVEL_ERROR, "Invalid argument at position %" PRIuS " when calling to metacallfv_s", iterator); + log_write("metacall", LOG_LEVEL_ERROR, "Invalid argument at position %" PRIuS " when calling to metacallfv_s with function <%s> and value <%p>", iterator, name, args[iterator]); return NULL; } @@ -2120,8 +2127,15 @@ void *metacallv_method(void *target, const char *name, method_invoke_ptr call, v { if (value_validate(args[iterator]) != 0) { + const char *name = method_name(m); + + if (name == NULL) + { + name = "anonymous"; + } + // TODO: Implement type error return a value - log_write("metacall", LOG_LEVEL_ERROR, "Invalid argument at position %" PRIuS " when calling to metacallv_method", iterator); + log_write("metacall", LOG_LEVEL_ERROR, "Invalid argument at position %" PRIuS " when calling to metacallv_method with method <%s> and value <%p>", iterator, name, args[iterator]); vector_destroy(v); return NULL; } diff --git a/source/ports/node_port/index.js b/source/ports/node_port/index.js index 23ff5d6830..3268b0e204 100644 --- a/source/ports/node_port/index.js +++ b/source/ports/node_port/index.js @@ -300,6 +300,21 @@ const metacall_inspect = () => { return {}; }; +/* Value API for handling pointers */ +const metacall_value_create_ptr = (ptr) => { + return addon.metacall_value_create_ptr(ptr); +}; + +/* Value API for getting the pointer to a value */ +const metacall_value_reference = (v) => { + return addon.metacall_value_reference(v); +}; + +/* Value API for getting the value of a pointer */ +const metacall_value_dereference = (ptr) => { + return addon.metacall_value_dereference(ptr); +}; + const metacall_handle = (tag, name) => { // TODO: This can be implemented with metacall_handle C API, meanwhile we use this trick const inspect = metacall_inspect(); @@ -337,6 +352,9 @@ const module_exports = { metacall_load_from_configuration, metacall_load_from_configuration_export, metacall_handle, + metacall_value_create_ptr, + metacall_value_reference, + metacall_value_dereference, /* TODO: Remove this from user or provide better ways of configuring logs */ metacall_logs: () => { diff --git a/source/reflect/source/reflect_value_type.c b/source/reflect/source/reflect_value_type.c index 6775aac44a..cc56921610 100644 --- a/source/reflect/source/reflect_value_type.c +++ b/source/reflect/source/reflect_value_type.c @@ -525,15 +525,29 @@ value value_from_double(value v, double d) value value_from_string(value v, const char *str, size_t length) { - if (v != NULL && str != NULL && length > 0) + if (v != NULL) { - size_t current_size = value_size(v); + if (str == NULL || length == 0) + { + return value_from(v, NULL, 1); + } + else + { + size_t current_size = value_type_size(v); - size_t bytes = length + 1; + size_t bytes = length + 1; - size_t size = (bytes <= current_size) ? bytes : current_size; + size_t size = (bytes <= current_size) ? bytes : current_size; - return value_from(v, str, size); + value_from(v, str, size); + + if (bytes > current_size) + { + char *str = value_to_string(v); + + str[size - 1] = '\0'; + } + } } return v; @@ -543,7 +557,7 @@ value value_from_buffer(value v, const void *buffer, size_t size) { if (v != NULL && buffer != NULL && size > 0) { - size_t current_size = value_size(v); + size_t current_size = value_type_size(v); size_t bytes = sizeof(char) * size; @@ -557,7 +571,7 @@ value value_from_array(value v, const value *values, size_t size) { if (v != NULL && values != NULL && size > 0) { - size_t current_size = value_size(v); + size_t current_size = value_type_size(v); size_t bytes = sizeof(const value) * size; @@ -571,7 +585,7 @@ value value_from_map(value v, const value *tuples, size_t size) { if (v != NULL && tuples != NULL && size > 0) { - size_t current_size = value_size(v); + size_t current_size = value_type_size(v); size_t bytes = sizeof(const value) * size; diff --git a/source/scripts/c/compiled/source/compiled.c b/source/scripts/c/compiled/source/compiled.c index b7468cde78..e41807a41d 100644 --- a/source/scripts/c/compiled/source/compiled.c +++ b/source/scripts/c/compiled/source/compiled.c @@ -1,4 +1,7 @@ +#include #include +#include +#include void compiled_print(int a, double b) { @@ -9,3 +12,101 @@ long compiled_sum(long a, long b) { return a + b; } + +char *return_text(void) +{ + static char input[] = "hello"; + return input; +} + +void process_text(char *input) +{ + printf("inside of compiled script '%s'\n", input); + assert(strcmp(input, "test_test") == 0); +} + +typedef struct data_t +{ + int value; +} * data_ptr_t; + +data_ptr_t alloc_data(void) +{ + data_ptr_t ptr = malloc(sizeof(struct data_t)); + + ptr->value = 0; + + printf("alloc_data %p\n", ptr); + + return ptr; +} + +void alloc_data_args(data_ptr_t *ptr) +{ + *ptr = malloc(sizeof(struct data_t)); + + (*ptr)->value = 0; + + printf("alloc_data_args %p\n", *ptr); + printf("alloc_data_args ref %p\n", ptr); +} + +int compare_data_value(data_ptr_t left, data_ptr_t right) +{ + printf("left %p\n", left); + printf("right %p\n", right); + assert(left == right); + return left == right; +} + +void set_data_value(data_ptr_t ptr, int value) +{ + printf("set_data_value %p\n", ptr); + ptr->value = value; +} + +int get_data_value(data_ptr_t ptr) +{ + printf("get_data_value %p\n", ptr); + return ptr->value; +} + +void free_data(data_ptr_t ptr) +{ + printf("free_data %p\n", ptr); + free(ptr); +} + +// TODO: When calling from NodeJS it does not work, +// NodeJS emmits double as a call, and this expects long, it needs a casting +void modify_int_ptr(long *l) +{ + printf("l %p\n", l); + printf("value %d\n", *l); + assert(*l == 324444L); + *l = 111L; +} + +void modify_double_ptr(double *d) +{ + printf("d %p\n", d); + printf("value %f\n", *d); + assert(*d == 324444.0); + *d = 111.0; +} + +void modify_str_ptr(char **str_ptr) +{ + static char new_str[] = "yeet"; + printf("(C) pointer %p\n", str_ptr); + printf("(C) string %p\n", (*str_ptr)); + printf("(C) string value %s\n", *str_ptr); + fflush(stdout); + assert(strcmp("asd", *str_ptr) == 0); + *str_ptr = new_str; + printf("(C) pointer %p\n", str_ptr); + printf("(C) string %p\n", (*str_ptr)); + printf("(C) string value %s\n", *str_ptr); + fflush(stdout); + assert(strcmp("yeet", *str_ptr) == 0); +} diff --git a/source/scripts/c/loadtest/source/loadtest.cpp b/source/scripts/c/loadtest/source/loadtest.cpp index 8dcb43dedb..5ddaf4b11c 100644 --- a/source/scripts/c/loadtest/source/loadtest.cpp +++ b/source/scripts/c/loadtest/source/loadtest.cpp @@ -1,4 +1,5 @@ #include "loadtest.h" +#include #include long call_cpp_func(void) @@ -23,6 +24,9 @@ int pair_list_init(pair_list **t) (*t)->pairs[i].d = (double)(((double)i) * 1.0); } + std::cout << "pair_list_init: " << t << std::endl; + std::cout << "pair_list_init: *(" << *t << ")" << std::endl; + return 0; } diff --git a/source/scripts/c/loadtest/source/loadtest.h b/source/scripts/c/loadtest/source/loadtest.h index 3e4f072748..4a93356958 100644 --- a/source/scripts/c/loadtest/source/loadtest.h +++ b/source/scripts/c/loadtest/source/loadtest.h @@ -11,7 +11,7 @@ extern "C" { #endif -#include +#include typedef struct { diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index 6e6ad2ffc9..3007e6cace 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -129,6 +129,7 @@ add_subdirectory(metacall_node_port_test) add_subdirectory(metacall_node_port_await_test) add_subdirectory(metacall_node_port_rs_test) add_subdirectory(metacall_node_port_c_lib_test) +add_subdirectory(metacall_node_port_c_test) add_subdirectory(metacall_node_python_port_mock_test) add_subdirectory(metacall_node_python_port_ruby_test) add_subdirectory(metacall_node_python_ruby_test) diff --git a/source/tests/metacall_c_lib_test/source/metacall_c_lib_test.cpp b/source/tests/metacall_c_lib_test/source/metacall_c_lib_test.cpp index cabe0100b3..0619a34754 100644 --- a/source/tests/metacall_c_lib_test/source/metacall_c_lib_test.cpp +++ b/source/tests/metacall_c_lib_test/source/metacall_c_lib_test.cpp @@ -51,6 +51,9 @@ TEST_F(metacall_c_lib_test, DefaultConstructor) ret = metacallv("pair_list_init", args_init); + std::cout << "args_init: " << args_init[0] << std::endl; + std::cout << "args_init: *(" << pair_list << ")" << std::endl; + EXPECT_NE((void *)NULL, (void *)ret); EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_INT); diff --git a/source/tests/metacall_c_test/source/metacall_c_test.cpp b/source/tests/metacall_c_test/source/metacall_c_test.cpp index 46cce764e4..a8c1ef2dd6 100644 --- a/source/tests/metacall_c_test/source/metacall_c_test.cpp +++ b/source/tests/metacall_c_test/source/metacall_c_test.cpp @@ -41,96 +41,210 @@ void *sum_callback(size_t argc, void *args[], void *data) return metacall_value_create_int(result); } +void *test_string_reference(size_t argc, void *args[], void *data) +{ + printf("ptr %p\n", args[0]); + fflush(stdout); + + void *string_value = metacall_value_to_ptr(args[0]); + + printf("string ptr %p\n", string_value); + printf("type id %s\n", metacall_value_type_name(string_value)); + fflush(stdout); + + char *str = metacall_value_to_string(string_value); + + (void)argc; + (void)data; + + printf("native string %s\n", str); + + EXPECT_STREQ("asd", str); + + static const char yeet[] = "yeet"; + + metacall_value_from_string(string_value, yeet, sizeof(yeet) - 1); + + printf("type id %s\n", metacall_value_type_name(string_value)); + printf("native string %s\n", str); + fflush(stdout); + + return metacall_value_create_null(); +} + TEST_F(metacall_c_test, DefaultConstructor) { ASSERT_EQ((int)0, (int)metacall_initialize()); + void *ret = NULL; + /* File */ - const char *c_scripts[] = { - "compiled.c" - }; + { + const char *c_scripts[] = { + "compiled.c" + }; - EXPECT_EQ((int)0, (int)metacall_load_from_file("c", c_scripts, sizeof(c_scripts) / sizeof(c_scripts[0]), NULL)); + EXPECT_EQ((int)0, (int)metacall_load_from_file("c", c_scripts, sizeof(c_scripts) / sizeof(c_scripts[0]), NULL)); - void *ret = metacall("compiled_sum", 3, 4); + ret = metacall("compiled_sum", 3, 4); - EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_NE((void *)NULL, (void *)ret); - EXPECT_EQ((long)metacall_value_to_long(ret), (long)7); + EXPECT_EQ((long)metacall_value_to_long(ret), (long)7); - metacall_value_destroy(ret); + metacall_value_destroy(ret); + } /* File with dependencies */ - const char *c_dep_scripts[] = { - "ffi.c", - "ffi.ld" - }; + { + const char *c_dep_scripts[] = { + "ffi.c", + "ffi.ld" + }; - /* Set dependency paths */ - EXPECT_EQ((int)0, (int)metacall_execution_path("c", LIBFFI_INCLUDE_DIR)); - EXPECT_EQ((int)0, (int)metacall_execution_path("c", LIBFFI_LIBRARY)); + /* Set dependency paths */ + EXPECT_EQ((int)0, (int)metacall_execution_path("c", LIBFFI_INCLUDE_DIR)); + EXPECT_EQ((int)0, (int)metacall_execution_path("c", LIBFFI_LIBRARY)); - EXPECT_EQ((int)0, (int)metacall_load_from_file("c", c_dep_scripts, sizeof(c_dep_scripts) / sizeof(c_dep_scripts[0]), NULL)); + EXPECT_EQ((int)0, (int)metacall_load_from_file("c", c_dep_scripts, sizeof(c_dep_scripts) / sizeof(c_dep_scripts[0]), NULL)); - ret = metacall("call_fp_address"); + ret = metacall("call_fp_address"); - EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_NE((void *)NULL, (void *)ret); - EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_PTR); + EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_PTR); - EXPECT_NE((void *)metacall_value_to_ptr(ret), (void *)NULL); + EXPECT_NE((void *)metacall_value_to_ptr(ret), (void *)NULL); - metacall_value_destroy(ret); + metacall_value_destroy(ret); - ret = metacall("int_type_renaming"); + ret = metacall("int_type_renaming"); - EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_NE((void *)NULL, (void *)ret); - EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_INT); + EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_INT); - EXPECT_EQ((int)metacall_value_to_int(ret), (int)345); + EXPECT_EQ((int)metacall_value_to_int(ret), (int)345); - metacall_value_destroy(ret); + metacall_value_destroy(ret); + } /* Native register */ - metacall_register("sum_callback", sum_callback, NULL, METACALL_INT, 2, METACALL_INT, METACALL_INT); + { + metacall_register("sum_callback", sum_callback, NULL, METACALL_INT, 2, METACALL_INT, METACALL_INT); - void *func = metacall_function("sum_callback"); + void *func = metacall_function("sum_callback"); - EXPECT_NE((void *)NULL, (void *)func); + EXPECT_NE((void *)NULL, (void *)func); - void *args[] = { - metacall_value_create_function(func) - }; + void *args[] = { + metacall_value_create_function(func) + }; - ret = metacallv_s("c_callback", args, 1); + ret = metacallv_s("c_callback", args, 1); - EXPECT_NE((void *)NULL, (void *)ret); + EXPECT_NE((void *)NULL, (void *)ret); - EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_INT); + EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_INT); - EXPECT_EQ((int)metacall_value_to_int(ret), (int)7); + EXPECT_EQ((int)metacall_value_to_int(ret), (int)7); - metacall_value_destroy(ret); + metacall_value_destroy(ret); - metacall_value_destroy(args[0]); + metacall_value_destroy(args[0]); + } /* Memory */ - // TODO - // const char c_buffer[] = { - // "int compiled_mult(int a, int b) { return a * b; }" - // }; + { + // TODO + // const char c_buffer[] = { + // "int compiled_mult(int a, int b) { return a * b; }" + // }; - // EXPECT_EQ((int)0, (int)metacall_load_from_memory("c", c_buffer, sizeof(c_buffer), NULL)); + // EXPECT_EQ((int)0, (int)metacall_load_from_memory("c", c_buffer, sizeof(c_buffer), NULL)); - // TODO - // void *ret = metacall("compiled_mult", 3, 4); + // TODO + // void *ret = metacall("compiled_mult", 3, 4); - // EXPECT_NE((void *)NULL, (void *)ret); + // EXPECT_NE((void *)NULL, (void *)ret); - // EXPECT_EQ((int)metacall_value_to_int(ret), (int)0); + // EXPECT_EQ((int)metacall_value_to_int(ret), (int)0); - // metacall_value_destroy(ret); + // metacall_value_destroy(ret); + } + + /* References (native) */ + { + static const char str[] = "asd"; + void *str_value = metacall_value_create_string(str, sizeof(str) - 1); + void *str_value_ref = metacall_value_reference(str_value); + + printf("ptr %p\n", str_value_ref); + printf("string %p\n", str_value); + printf("string str %s\n", metacall_value_to_string(str_value)); + fflush(stdout); + + { + void *new_str_value = metacall_value_to_ptr(str_value_ref); + char *new_str = metacall_value_to_string(new_str_value); + + EXPECT_STREQ("asd", new_str); + } + + void *args[] = { + str_value_ref + }; + + metacall_register("test_string_reference", test_string_reference, NULL, METACALL_NULL, 1, METACALL_PTR); + + ret = metacallv_s("test_string_reference", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + + EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_NULL); + + metacall_value_destroy(ret); + + printf("type id %s\n", metacall_value_type_name(str_value)); + fflush(stdout); + + // It chops the string because it has a fixed size from 'asd' + EXPECT_STREQ(metacall_value_to_string(str_value), "yee"); + + metacall_value_destroy(str_value); + metacall_value_destroy(str_value_ref); + } + + /* References (c) */ + { + static const char str[] = "asd"; + void *str_value = metacall_value_create_string(str, sizeof(str) - 1); + void *str_value_ref = metacall_value_reference(str_value); + + printf("(R) ptr %p\n", str_value_ref); + printf("(R) string ptr %p\n", str_value); + printf("(R) string str %s\n", metacall_value_to_string(str_value)); + fflush(stdout); + + void *args[] = { + str_value_ref + }; + + ret = metacallv_s("modify_str_ptr", args, 1); + + EXPECT_NE((void *)NULL, (void *)ret); + + EXPECT_EQ((enum metacall_value_id)metacall_value_id(ret), (enum metacall_value_id)METACALL_NULL); + + metacall_value_destroy(ret); + + char *str_value_deref = static_cast(metacall_value_dereference(str_value_ref)); + + EXPECT_STREQ(str_value_deref, "yeet"); + + metacall_value_destroy(str_value); + metacall_value_destroy(str_value_ref); + } /* Print inspect information */ { diff --git a/source/tests/metacall_node_port_c_test/CMakeLists.txt b/source/tests/metacall_node_port_c_test/CMakeLists.txt new file mode 100644 index 0000000000..6f03f73dcf --- /dev/null +++ b/source/tests/metacall_node_port_c_test/CMakeLists.txt @@ -0,0 +1,161 @@ +# Check if this loader is enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_NODE OR NOT OPTION_BUILD_LOADERS_C OR NOT OPTION_BUILD_PORTS OR NOT OPTION_BUILD_PORTS_NODE) + return() +endif() + +# +# Executable name and options +# + +# Target name +set(target metacall-node-port-c-test) +message(STATUS "Test ${target}") + +# +# Compiler warnings +# + +include(Warnings) + +# +# Compiler security +# + +include(SecurityFlags) + +# +# Sources +# + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(sources + ${source_path}/main.cpp + ${source_path}/metacall_node_port_c_test.cpp +) + +# Group source files +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +# +# Create executable +# + +# Build executable +add_executable(${target} + ${sources} +) + +# Create namespaced alias +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include +) + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + + GTest + + ${META_PROJECT_NAME}::metacall +) + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} + + # NodeJS Port path + METACALL_NODE_PORT_PATH="${CMAKE_SOURCE_DIR}/source/ports/node_port/index.js" +) + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + +# +# Compile features +# + +target_compile_features(${target} + PRIVATE + cxx_std_17 +) + +# +# Linker options +# + +target_link_options(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + +# +# Define test +# + +add_test(NAME ${target} + COMMAND $ +) + +# +# Define dependencies +# + +add_dependencies(${target} + node_port + node_loader + c_loader +) + +# +# Define test properties +# + +set_property(TEST ${target} + PROPERTY LABELS ${target} +) + +include(TestEnvironmentVariables) + +test_environment_variables(${target} + "" + ${TESTS_ENVIRONMENT_VARIABLES} +) diff --git a/source/tests/metacall_node_port_c_test/source/main.cpp b/source/tests/metacall_node_port_c_test/source/main.cpp new file mode 100644 index 0000000000..5820341294 --- /dev/null +++ b/source/tests/metacall_node_port_c_test/source/main.cpp @@ -0,0 +1,28 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2025 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/source/tests/metacall_node_port_c_test/source/metacall_node_port_c_test.cpp b/source/tests/metacall_node_port_c_test/source/metacall_node_port_c_test.cpp new file mode 100644 index 0000000000..19286192f3 --- /dev/null +++ b/source/tests/metacall_node_port_c_test/source/metacall_node_port_c_test.cpp @@ -0,0 +1,114 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2025 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include + +class metacall_node_port_c_test : public testing::Test +{ +public: +}; + +TEST_F(metacall_node_port_c_test, DefaultConstructor) +{ + metacall_print_info(); + + ASSERT_EQ((int)0, (int)metacall_initialize()); + + static const char buffer[] = + "const { strictEqual } = require('assert');\n" + "const { metacall_load_from_file_export, metacall_value_create_ptr, metacall_value_reference, metacall_value_dereference } = require('" METACALL_NODE_PORT_PATH "');\n" + "const { return_text, process_text, modify_double_ptr, modify_str_ptr, compare_data_value, alloc_data, alloc_data_args, set_data_value, get_data_value, free_data } = metacall_load_from_file_export('c', ['compiled.c']);\n" + // Test strings + "const result = return_text();\n" + "console.log(`'${result}'`);\n" + "strictEqual(result, 'hello');\n" + "console.log(result);\n" + "process_text('test_test');\n" + // Test return pointers + "data_ptr = alloc_data();\n" + "console.log(data_ptr);\n" + "set_data_value(data_ptr, 12);\n" + "strictEqual(get_data_value(data_ptr), 12);\n" + "free_data(data_ptr);\n" + // Test passing reference by arguments + "double_val = 324444.0;\n" + "double_val_ref = metacall_value_reference(double_val);\n" + "modify_double_ptr(double_val_ref);\n" + "double_val_deref = metacall_value_dereference(double_val_ref);\n" + "strictEqual(double_val_deref, 111.0);\n" + // TODO: Test passing reference by arguments string +#if 0 + "str_val = 'asd';\n" + "str_val_ref = metacall_value_reference(str_val);\n" + "console.log(str_val);\n" + "console.log(str_val_ref);\n" + "modify_str_ptr(str_val_ref);\n" + "console.log(str_val_ref);\n" + "str_val_deref = metacall_value_dereference(str_val_ref);\n" + "console.log(str_val_deref);\n" + "strictEqual(str_val_deref, 'yeet');\n" +#endif + // Test passing reference of structs by arguments (with no args on create ptr) + "data_ptr = metacall_value_create_ptr();\n" + "data_ptr_ref = metacall_value_reference(data_ptr);\n" + "console.log(data_ptr);\n" + "console.log(data_ptr_ref);\n" + "alloc_data_args(data_ptr_ref);\n" + "alloc_data_ptr = metacall_value_dereference(data_ptr_ref);\n" + "console.log(alloc_data_ptr);\n" + "set_data_value(alloc_data_ptr, 12);\n" + "strictEqual(get_data_value(alloc_data_ptr), 12);\n" + "free_data(alloc_data_ptr);\n" + // Test passing reference of structs by arguments (with undefined arg on create ptr) + "data_ptr = metacall_value_create_ptr(undefined);\n" + "data_ptr_ref = metacall_value_reference(data_ptr);\n" + "console.log(data_ptr);\n" + "console.log(data_ptr_ref);\n" + "alloc_data_args(data_ptr_ref);\n" + "alloc_data_ptr = metacall_value_dereference(data_ptr_ref);\n" + "console.log(alloc_data_ptr);\n" + "set_data_value(alloc_data_ptr, 12);\n" + "strictEqual(get_data_value(alloc_data_ptr), 12);\n" + "free_data(alloc_data_ptr);\n" + // Test passing reference of structs by arguments (with another pointer arg on create ptr) + "data_ptr = metacall_value_create_ptr(undefined);\n" + "copy_data_ptr = metacall_value_create_ptr(data_ptr);\n" + "console.log(data_ptr);\n" + "console.log(copy_data_ptr);\n" + "strictEqual(compare_data_value(data_ptr, copy_data_ptr), 1);\n" + "data_ptr_ref = metacall_value_reference(copy_data_ptr);\n" + "console.log(data_ptr);\n" + "console.log(data_ptr_ref);\n" + "alloc_data_args(data_ptr_ref);\n" + "alloc_data_ptr = metacall_value_dereference(data_ptr_ref);\n" + "console.log(alloc_data_ptr);\n" + "set_data_value(alloc_data_ptr, 12);\n" + "strictEqual(get_data_value(alloc_data_ptr), 12);\n" + "free_data(alloc_data_ptr);\n" + "\n"; + + ASSERT_EQ((int)0, (int)metacall_load_from_memory("node", buffer, sizeof(buffer), NULL)); + + metacall_destroy(); +} diff --git a/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp b/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp index fd6214d1d5..51f29bb613 100644 --- a/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp +++ b/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp @@ -60,8 +60,9 @@ TEST_F(metacall_python_port_pointer_test, DefaultConstructor) static const char buffer[] = "import sys\n" "sys.path.insert(0, '" METACALL_PYTHON_PORT_PATH "')\n" - "from metacall import metacall_load_from_package, metacall, metacall_value_create_ptr, metacall_value_reference, metacall_value_dereference\n" + "from metacall import metacall_load_from_package, metacall_load_from_file, metacall, metacall_value_create_ptr, metacall_value_reference, metacall_value_dereference\n" "metacall_load_from_package('c', 'loadtest')\n" + "metacall_load_from_file('c', ['compiled.c'])\n" "def test_int_ptr() -> int:\n" " print('Test start')\n" @@ -81,6 +82,24 @@ TEST_F(metacall_python_port_pointer_test, DefaultConstructor) " return int_val_deref\n" + "def test_str_ptr() -> str:\n" + " print('Test start')\n" + " sys.stdout.flush()\n" + + " str_val = 'asd'\n" + " str_val_ref = metacall_value_reference(str_val)\n" + + " print(str_val_ref)\n" + " sys.stdout.flush()\n" + + " metacall('modify_str_ptr', str_val_ref)\n" + " str_val_deref = metacall_value_dereference(str_val_ref)\n" + + " print(str_val, '!=', str_val_deref)\n" + " sys.stdout.flush()\n" + + " return str_val_deref\n" + "def test_struct_ptr() -> float:\n" " print('Test start')\n" " sys.stdout.flush()\n" @@ -110,6 +129,17 @@ TEST_F(metacall_python_port_pointer_test, DefaultConstructor) metacall_value_destroy(ret); + ret = metacall("test_str_ptr"); + + ASSERT_EQ((enum metacall_value_id)METACALL_STRING, (enum metacall_value_id)metacall_value_id(ret)); + + // TODO: Implement construction of a pointer after the call +#if 0 + EXPECT_STREQ("yeet", metacall_value_to_string(ret)); +#endif + + metacall_value_destroy(ret); + ret = metacall("test_struct_ptr"); ASSERT_EQ((enum metacall_value_id)METACALL_DOUBLE, (enum metacall_value_id)metacall_value_id(ret));