From 71994be440addee1cc4352c6877ef39d3819e4b5 Mon Sep 17 00:00:00 2001 From: James Batt Date: Mon, 9 Jul 2018 19:32:05 +1000 Subject: [PATCH] wip --- __tests__/types.test.js | 28 +++ binding.gyp | 7 +- package-lock.json | 28 ++- src/arguments.cpp | 261 ++++++++++++++++----------- src/arguments.h | 22 ++- src/closure.cpp | 2 +- src/internal/PersistentObjectStore.h | 7 +- src/parameter.cpp | 102 +++++++++++ src/parameter.h | 44 +++++ src/type.cpp | 135 ++++++++++++++ src/type.h | 10 + src/types/function.cpp | 31 +++- src/types/param_spec.cpp | 1 - src/types/struct.cpp | 5 +- src/types/struct.h | 2 +- src/util.h | 6 + src/values.cpp | 75 ++++++++ src/values.h | 3 + 18 files changed, 629 insertions(+), 140 deletions(-) create mode 100644 __tests__/types.test.js create mode 100644 src/parameter.cpp create mode 100644 src/parameter.h create mode 100644 src/type.cpp create mode 100644 src/type.h diff --git a/__tests__/types.test.js b/__tests__/types.test.js new file mode 100644 index 0000000..4eb4418 --- /dev/null +++ b/__tests__/types.test.js @@ -0,0 +1,28 @@ +const { load } = require('../'); + +const GLib = load('GLib'); +const Gtk = load('Gtk', '3.0'); + +describe('types', () => { + // test('functions can receive IN arrays', () => { + // const data = [104, 101, 108, 108, 111]; + // const result = GLib.base64Encode(data, data.length); + // expect(typeof(result)).toEqual('string'); + // expect(result).toEqual(Buffer.from('hello').toString('base64')); + // }); + + // test('functions can return OUT arrays', () => { + // const filepath = __filename; + // const result = GLib.fileGetContents(filepath); + // expect(result.length).toBe(3); + // expect(result[0]).toBe(true); + // expect(result[1].length).toEqual(result[2]); + // }); + + test('functions can receive and return INOUT arrays', () => { + const result = Gtk.init(['argument1', '--gtk-debug', 'misc', 'argument2']); + expect(result.length).toEqual(2); + expect(result[0]).toEqual('argument1'); + expect(result[1]).toEqual('argument2'); + }); +}); diff --git a/binding.gyp b/binding.gyp index b9d7211..6aff372 100644 --- a/binding.gyp +++ b/binding.gyp @@ -15,6 +15,8 @@ 'src/types/param_spec.cpp', 'src/loop.cpp', 'src/closure.cpp', + 'src/parameter.cpp', + 'src/type.cpp', ], 'include_dirs': [ ' #include #include #include "closure.h" #include "exceptions.h" +#include "type.h" #include "types/object.h" #include "types/struct.h" - -using namespace v8; +#include "util.h" +#include "values.h" namespace gir { +using namespace v8; + Args::Args(GICallableInfo *callable_info) : callable_info(callable_info) { g_base_info_ref(callable_info); // because we keep a reference to the info we need to tell glib } @@ -33,31 +35,41 @@ void Args::load_js_arguments(const Nan::FunctionCallbackInfo &js_call // convert it into a GIArgument, adding it to the in/out args array depending // on it's direction. for (guint8 i = 0; i < gi_argc; i++) { - GIArgInfo argument_info; - g_callable_info_load_arg(this->callable_info.get(), i, &argument_info); - GIDirection argument_direction = g_arg_info_get_direction(&argument_info); + GIArgInfo *argument_info = g_callable_info_get_arg(this->callable_info.get(), i); - if (argument_direction == GI_DIRECTION_IN) { - GIArgument argument = Args::arg_to_g_type(argument_info, js_callback_info[i]); - this->in.push_back(argument); - } - - if (argument_direction == GI_DIRECTION_OUT) { - GIArgument argument = this->get_out_argument_value(argument_info); - this->out.push_back(argument); - } + switch (g_arg_info_get_direction(argument_info)) { + case GI_DIRECTION_IN: + this->params.push_back(new InParameter(Args::arg_to_g_type(*argument_info, js_callback_info[i]))); + break; - if (argument_direction == GI_DIRECTION_INOUT) { - GIArgument argument = Args::arg_to_g_type(argument_info, js_callback_info[i]); - this->in.push_back(argument); + case GI_DIRECTION_OUT: + this->params.push_back(new OutParameter(argument_info)); + break; - // TODO: is it correct to handle INOUT arguments like IN args? - // do we need to handle callee (native) allocates or empty input - // GIArguments like we do with OUT args? i'm just assuming this is how it - // should work (treating it like an IN arg). Hopfully I can find some - // examples to make some test cases asserting the correct behaviour - this->out.push_back(argument); + case GI_DIRECTION_INOUT: + this->params.push_back(new InOutParameter(Args::arg_to_g_type(*argument_info, js_callback_info[i]))); + break; } + + // if (argument_direction == GI_DIRECTION_OUT) { + // GIArgument argument = + // this->out.push_back(argument); + // this->all.push_back(argument); + // } + + // if (argument_direction == GI_DIRECTION_INOUT) { + // GIArgument argument = Args::arg_to_g_type(argument_info, js_callback_info[i]); + // this->in.push_back(argument); + + // // TODO: is it correct to handle INOUT arguments like IN args? + // // do we need to handle callee (native) allocates or empty input + // // GIArguments like we do with OUT args? i'm just assuming this is how it + // // should work (treating it like an IN arg). Hopfully I can find some + // // examples to make some test cases asserting the correct behaviour + // this->out.push_back(argument); + + // this->all.push_back(argument); + // } } } @@ -70,67 +82,39 @@ void Args::load_context(GObject *this_object) { GIArgument this_object_argument = { .v_pointer = this_object, }; - this->in.insert(this->in.begin(), this_object_argument); + this->params.insert(this->params.begin(), new InParameter(this_object_argument)); } -GIArgument Args::get_out_argument_value(GIArgInfo &argument_info) { - GITypeInfo argument_type_info; - g_arg_info_load_type(&argument_info, &argument_type_info); - if (g_arg_info_is_caller_allocates(&argument_info)) { - GITypeTag arg_type_tag = g_type_info_get_tag(&argument_type_info); - // If the caller is responsible for allocating the out arguments memeory - // then we'll have to look up the argument's type infomation and allocate - // a slice of memory for the GIArgument's .v_pointer (native function will - // fill it up) - if (arg_type_tag == GI_TYPE_TAG_INTERFACE) { - GIRInfoUniquePtr argument_interface_info = GIRInfoUniquePtr(g_type_info_get_interface(&argument_type_info)); - GIInfoType argument_interface_type = g_base_info_get_type(argument_interface_info.get()); - gsize argument_size; - - if (argument_interface_type == GI_INFO_TYPE_STRUCT) { - argument_size = g_struct_info_get_size((GIStructInfo *)argument_interface_info.get()); - } else if (argument_interface_type == GI_INFO_TYPE_UNION) { - argument_size = g_union_info_get_size((GIUnionInfo *)argument_interface_info.get()); - } else { - stringstream message; - message << "type \"" << g_type_tag_to_string(arg_type_tag) << "\" for out caller-allocates"; - message << " Expected a struct or union."; - throw UnsupportedGIType(message.str()); - } +vector Args::get_in_args() { + vector in_params; + copy_if(this->params.begin(), this->params.end(), back_inserter(in_params), [](auto param) { + return Util::instance_of(param) || Util::instance_of(param); + }); + vector in_args; + transform(in_params.begin(), in_params.end(), back_inserter(in_args), [](auto param) { + return param->get_argument(); + }); + return in_args; +} - GIArgument argument; - // FIXME: who deallocates? - // I imagine the original function caller (in JS land) will need - // to use the structure that the native function puts into this - // slice of memory, meaning we can't deallocate when Args is destroyed. - // Perhaps we should research into GJS and PyGObject to understand - // the problem of "out arguments with caller allocation" better. - // Some thoughts: - // 1. if the data is **copied** into a gir object/struct when passed back - // to JS then we can safely implement a custom deleter for Args.out - // that cleans this up. - // 2. if the pointer to the data is passed to a gir object/struct when - // passed back to JS then that JS object should own it and be - // responsible for cleaning it up. - // * both choices have caveats so it's worth understanding the - // implications - // of each, or other options to solve this leak! - // * from reading GJS code, it seems like they copy the data before - // passing - // * to JS meaning option 1. - argument.v_pointer = g_slice_alloc0(argument_size); - return argument; - } else { - stringstream message; - message << "type \"" << g_type_tag_to_string(arg_type_tag) << "\" for out caller-allocates"; - throw UnsupportedGIType(message.str()); - } - } - // else, if we're not responsible for allocation then we can just return an - // empty GIArgument with a NULL .v_pointer (native call will set it with a - // memory location) - GIArgument argument = {.v_pointer = nullptr}; - return argument; +vector Args::get_out_args() { + vector out_params; + copy_if(this->params.begin(), this->params.end(), back_inserter(out_params), [](auto param) { + return Util::instance_of(param) || Util::instance_of(param); + }); + vector in_args; + transform(out_params.begin(), out_params.end(), back_inserter(in_args), [](auto param) { + return param->get_argument(); + }); + return in_args; +} + +vector Args::get_all_args() { + vector args; + transform(this->params.begin(), this->params.end(), back_inserter(args), [](auto param) { + return param->get_argument(); + }); + return args; } GIArgument Args::arg_to_g_type(GIArgInfo &argument_info, Local js_value) { @@ -231,6 +215,9 @@ GIArgument Args::type_to_g_type(GITypeInfo &argument_type_info, Local js_ argument_value.v_double = js_value->NumberValue(); break; + case GI_TYPE_TAG_ARRAY: + return Args::to_g_type_array(js_value, &argument_type_info); + case GI_TYPE_TAG_UTF8: case GI_TYPE_TAG_FILENAME: if (!js_value->IsString()) { @@ -314,40 +301,108 @@ GIArgument Args::type_to_g_type(GITypeInfo &argument_type_info, Local js_ return argument_value; } +int Args::get_g_type_array_length(GICallableInfo *callable_info, + vector call_args, + GIArgument *array_arg, + GITypeInfo *array_arg_type) { + int length_arg_pos = g_type_info_get_array_length(array_arg_type); + if (length_arg_pos == -1) { + return -1; + } + GIArgInfo length_arg; + g_callable_info_load_arg(callable_info, length_arg_pos, &length_arg); + GIDirection length_direction = g_arg_info_get_direction(&length_arg); + if (length_direction == GI_DIRECTION_OUT || length_direction == GI_DIRECTION_INOUT) { + return *static_cast(call_args[length_arg_pos].v_pointer); + } + return call_args[length_arg_pos].v_int; +} + Local Args::from_g_type_array(GIArgument *arg, GITypeInfo *type, int array_length) { GIArrayType array_type_info = g_type_info_get_array_type(type); auto element_type_info = GIRInfoUniquePtr(g_type_info_get_param_type(type, 0)); - GITypeTag param_tag = g_type_info_get_tag(element_type_info.get()); + + void *native_array = arg->v_pointer; + int length = array_length; + gsize element_size = get_type_size(element_type_info.get()); switch (array_type_info) { case GI_ARRAY_TYPE_C: - if (g_type_info_is_zero_terminated(type)) { - GIArgument element; - gpointer *native_array = (gpointer *)arg->v_pointer; - Local js_array = Nan::New(); - for (int i = 0; native_array[i]; i++) { - element.v_pointer = native_array[i]; - Local js_element = Args::from_g_type(&element, element_type_info.get(), 0); - js_array->Set(i, js_element); + if (length == -1) { + // first we handle the case where there was no array length + // provided when calling this method. + // this is the case when the array comes from a function call + // that doesn't have an out argument for the array's length. + // i.e. it returns a null terminated array or a fixed length array. + if (g_type_info_is_zero_terminated(type)) { + // if the array is null terminated we can use a standard + // string length method to find the array length + length = g_strv_length(static_cast(native_array)); + } else { + // otherwise, if there's no length (length == -1) and + // the array wasn't null terminated, it must have a fixed + // size. + length = g_type_info_get_array_fixed_size(type); + if (length == -1) { + // otherwise, if the array wasn't fixed size then we've exhausted + // all the ways we available to find the array length. + stringstream message; + message << "unable to determine array length for C array"; + throw UnsupportedGIType(message.str()); + } } - return js_array; - } else { - // use array_length param once the layers above this - // pas it correctly. - // the length param comes from the native function call's - // out arguments but, currently, it doesn't get passed own - // here correctly. - throw UnsupportedGIType("Converting non null terminated arrays is not yet supported"); } break; + case GI_ARRAY_TYPE_ARRAY: + case GI_ARRAY_TYPE_BYTE_ARRAY: { + GArray *g_array = static_cast(native_array); + native_array = g_array->data; + length = g_array->len; + element_size = g_array_get_element_size(g_array); + } break; + case GI_ARRAY_TYPE_PTR_ARRAY: { + GPtrArray *ptr_array = static_cast(native_array); + native_array = ptr_array->pdata; + length = ptr_array->len; + element_size = sizeof(gpointer); + break; + } default: throw UnsupportedGIType("cannot convert native array type"); } - stringstream message; - message << "Converting array of type '" << g_type_tag_to_string(param_tag); - message << "' is not supported"; - throw UnsupportedGIType(message.str()); + if (native_array == nullptr || length == 0) { + return Nan::New(); // an empty array + } + + GIArgument element; + Local js_array = Nan::New(); + for (int i = 0; i < length; i++) { + // void** pointer = static_cast(static_cast(native_array) + i * element_size); + void **pointer = static_cast(native_array + i * element_size); + memcpy(&element, pointer, element_size); + Nan::Set(js_array, i, Args::from_g_type(&element, element_type_info.get())); + } + return js_array; +} + +GIArgument Args::to_g_type_array(Local value, GITypeInfo *info) { + GIArrayType array_type = g_type_info_get_array_type(info); + GIArgument arg; + switch (array_type) { + case GI_ARRAY_TYPE_C: + // TODO: what deallocates the memory created by GIRValue::to_c_array + arg.v_pointer = GIRValue::to_c_array(value, info); + break; + case GI_ARRAY_TYPE_ARRAY: + case GI_ARRAY_TYPE_BYTE_ARRAY: + // TODO: what deallocates the memory created by GIRValue::to_g_array + arg.v_pointer = GIRValue::to_g_array(value, info); + break; + default: + throw UnsupportedGIType("unsupported array type"); + } + return arg; } // TODO: refactor this function and most of the code below this. diff --git a/src/arguments.h b/src/arguments.h index a06ad0b..67506af 100644 --- a/src/arguments.h +++ b/src/arguments.h @@ -5,6 +5,8 @@ #include #include #include + +#include "parameter.h" #include "util.h" namespace gir { @@ -14,28 +16,30 @@ using namespace v8; class Args { public: - vector in; - vector out; + vector params; Args(GICallableInfo *callable_info); void load_js_arguments(const Nan::FunctionCallbackInfo &js_callback_info); void load_context(GObject *this_object); + vector get_in_args(); + vector get_out_args(); + vector get_all_args(); private: GIRInfoUniquePtr callable_info; - GIArgument get_in_argument_value(const Local &js_value, GIArgInfo &argument_info); - GIArgument get_out_argument_value(GIArgInfo &argument_info); static GITypeTag map_g_type_tag(GITypeTag type); public: - // these functions are legacy and need to be refactored - // there are many missing features within them as well such as missing type conversions (types that aren't supported - // like structs.) static GIArgument arg_to_g_type(GIArgInfo &argument_info, Local js_value); static GIArgument type_to_g_type(GITypeInfo &argument_type_info, Local js_value); - static Local from_g_type_array(GIArgument *arg, GIArgInfo *info, int array_length); - static Local from_g_type(GIArgument *arg, GITypeInfo *type_info, int array_length); + static int get_g_type_array_length(GICallableInfo *callable_info, + vector call_args, + GIArgument *array_arg, + GITypeInfo *array_arg_type); + static Local from_g_type_array(GIArgument *arg, GIArgInfo *info, int array_length = -1); + static GIArgument to_g_type_array(Local value, GITypeInfo *info); + static Local from_g_type(GIArgument *arg, GITypeInfo *type_info, int array_length = -1); }; } // namespace gir diff --git a/src/closure.cpp b/src/closure.cpp index de69d67..2229a0a 100644 --- a/src/closure.cpp +++ b/src/closure.cpp @@ -55,7 +55,7 @@ void GIRClosure::ffi_closure_callback(ffi_cif *cif, void *result, void **args, g // skip void arguments continue; } - js_args.push_back(Args::from_g_type(gi_args[i], arg_type_info.get(), 0)); + js_args.push_back(Args::from_g_type(gi_args[i], arg_type_info.get())); } } Local js_callback = Nan::New(gir_closure->callback); diff --git a/src/internal/PersistentObjectStore.h b/src/internal/PersistentObjectStore.h index 2d446e7..8678687 100644 --- a/src/internal/PersistentObjectStore.h +++ b/src/internal/PersistentObjectStore.h @@ -4,13 +4,14 @@ namespace gir { -template class PersistentObjectStore { +template +class PersistentObjectStore { public: PersistentObjectStore() = default; ~PersistentObjectStore() { for (auto &keyValue : persistentObjects) - keyValue.second.Empty(); // Disposes of the persistent's handle + keyValue.second.Empty(); // Disposes of the persistent's handle }; PersistentType &at(const KeyType &key) { @@ -29,4 +30,4 @@ template class PersistentObjectStore { std::map persistentObjects; }; -} +} // namespace gir diff --git a/src/parameter.cpp b/src/parameter.cpp new file mode 100644 index 0000000..6f9e7e0 --- /dev/null +++ b/src/parameter.cpp @@ -0,0 +1,102 @@ +#include + +#include "exceptions.h" +#include "parameter.h" + +namespace gir { + +using namespace std; + +InParameter::~InParameter() {} + +GIArgument InParameter::get_argument() { + return this->arg; +} + +OutParameter::OutParameter(GIArgInfo *info) { + g_base_info_ref(info); + this->info = info; + this->argument = GIArgument{}; + GITypeInfo argument_type_info; + g_arg_info_load_type(this->info, &argument_type_info); + if (g_arg_info_is_caller_allocates(this->info)) { + GITypeTag arg_type_tag = g_type_info_get_tag(&argument_type_info); + // If the caller is responsible for allocating the out arguments memeory + // then we'll have to look up the argument's type infomation and allocate + // a slice of memory for the GIArgument's .v_pointer (native function will + // fill it up) + if (arg_type_tag == GI_TYPE_TAG_INTERFACE) { + GIRInfoUniquePtr argument_interface_info = GIRInfoUniquePtr(g_type_info_get_interface(&argument_type_info)); + GIInfoType argument_interface_type = g_base_info_get_type(argument_interface_info.get()); + gsize argument_size; + + if (argument_interface_type == GI_INFO_TYPE_STRUCT) { + argument_size = g_struct_info_get_size(static_cast(argument_interface_info.get())); + } else if (argument_interface_type == GI_INFO_TYPE_UNION) { + argument_size = g_union_info_get_size(static_cast(argument_interface_info.get())); + } else { + stringstream message; + message << "type \"" << g_type_tag_to_string(arg_type_tag) << "\" for out caller-allocates"; + message << " Expected a struct or union."; + throw UnsupportedGIType(message.str()); + } + + // FIXME: who deallocates? + // I imagine the original function caller (in JS land) will need + // to use the structure that the native function puts into this + // slice of memory, meaning we can't deallocate when Args is destroyed. + // Perhaps we should research into GJS and PyGObject to understand + // the problem of "out arguments with caller allocation" better. + // Some thoughts: + // 1. if the data is **copied** into a gir object/struct when passed back + // to JS then we can safely implement a custom deleter for Args.out + // that cleans this up. + // 2. if the pointer to the data is passed to a gir object/struct when + // passed back to JS then that JS object should own it and be + // responsible for cleaning it up. + // * both choices have caveats so it's worth understanding the + // implications + // of each, or other options to solve this leak! + // * from reading GJS code, it seems like they copy the data before + // passing + // * to JS meaning option 1. + this->argument.v_pointer = g_slice_alloc0(argument_size); + return; + } else { + stringstream message; + message << "type \"" << g_type_tag_to_string(arg_type_tag) << "\" for out caller-allocates"; + throw UnsupportedGIType(message.str()); + } + } else { + // if the caller doesn't allocate then we need to create an empty GIArgument on the .v_pointer + // for the callee to dump their result into. + this->argument.v_pointer = new GIArgument{}; + return; + } + return; +} + +OutParameter::~OutParameter() { + if (this->info != nullptr) { + g_base_info_unref(this->info); + } + if (this->argument.v_pointer != nullptr) { + // TODO: free data depending on ownership rules encoded in type info. + } +} + +GIArgument OutParameter::get_argument() { + return this->argument; +} + +InOutParameter::InOutParameter(GIArgument) : arg(arg) { + this->out_arg = GIArgument{}; + this->out_arg.v_pointer = this->arg.v_pointer; + this->arg.v_pointer = &this->out_arg; +} + +GIArgument InOutParameter::get_argument() { + return this->arg; +} + +} // namespace gir diff --git a/src/parameter.h b/src/parameter.h new file mode 100644 index 0000000..343462a --- /dev/null +++ b/src/parameter.h @@ -0,0 +1,44 @@ +#pragma once + +#include "girepository.h" +#include "util.h" + +namespace gir { + +class Parameter { +public: + virtual GIArgument get_argument() = 0; +}; + +class InParameter : public Parameter { +private: + GIArgument arg; + +public: + InParameter(GIArgument arg) : arg(arg){}; + ~InParameter(); + GIArgument get_argument(); +}; + +class OutParameter : public Parameter { +private: + GIArgInfo *info = nullptr; + GIArgument argument; + +public: + OutParameter(GIArgInfo *info); + ~OutParameter(); + GIArgument get_argument(); +}; + +class InOutParameter : public Parameter { +private: + GIArgument arg; + GIArgument out_arg; + +public: + InOutParameter(GIArgument arg); + GIArgument get_argument(); +}; + +} // namespace gir diff --git a/src/type.cpp b/src/type.cpp new file mode 100644 index 0000000..e475533 --- /dev/null +++ b/src/type.cpp @@ -0,0 +1,135 @@ +#include + +#include "exceptions.h" +#include "type.h" +#include "util.h" + +namespace gir { + +using namespace std; + +gsize get_type_size(GITypeInfo *type_info) { + gsize size = 0; + + GITypeTag type_tag = g_type_info_get_tag(type_info); + + switch (type_tag) { + case GI_TYPE_TAG_BOOLEAN: + case GI_TYPE_TAG_INT8: + case GI_TYPE_TAG_UINT8: + case GI_TYPE_TAG_INT16: + case GI_TYPE_TAG_UINT16: + case GI_TYPE_TAG_INT32: + case GI_TYPE_TAG_UINT32: + case GI_TYPE_TAG_INT64: + case GI_TYPE_TAG_UINT64: + case GI_TYPE_TAG_FLOAT: + case GI_TYPE_TAG_DOUBLE: + case GI_TYPE_TAG_GTYPE: + case GI_TYPE_TAG_UNICHAR: + return get_type_tag_size(type_tag); + case GI_TYPE_TAG_INTERFACE: { + auto info = GIRInfoUniquePtr(g_type_info_get_interface(type_info)); + GIInfoType info_type = g_base_info_get_type(info.get()); + + switch (info_type) { + case GI_INFO_TYPE_STRUCT: + if (g_type_info_is_pointer(type_info)) { + return sizeof(gpointer); + } else { + return g_struct_info_get_size(static_cast(info.get())); + } + break; + case GI_INFO_TYPE_UNION: + if (g_type_info_is_pointer(type_info)) { + return sizeof(gpointer); + } else { + return g_union_info_get_size(static_cast(info.get())); + } + break; + case GI_INFO_TYPE_ENUM: + case GI_INFO_TYPE_FLAGS: + if (g_type_info_is_pointer(type_info)) { + return sizeof(gpointer); + } else { + GITypeTag type_tag = g_enum_info_get_storage_type(static_cast(info.get())); + return get_type_tag_size(type_tag); + } + break; + case GI_INFO_TYPE_BOXED: + case GI_INFO_TYPE_OBJECT: + case GI_INFO_TYPE_INTERFACE: + case GI_INFO_TYPE_CALLBACK: + return sizeof(gpointer); + case GI_INFO_TYPE_VFUNC: + case GI_INFO_TYPE_FUNCTION: + case GI_INFO_TYPE_CONSTANT: + case GI_INFO_TYPE_VALUE: + case GI_INFO_TYPE_SIGNAL: + case GI_INFO_TYPE_PROPERTY: + case GI_INFO_TYPE_FIELD: + case GI_INFO_TYPE_ARG: + case GI_INFO_TYPE_TYPE: + case GI_INFO_TYPE_INVALID: + case GI_INFO_TYPE_UNRESOLVED: + default: + stringstream message; + message << "cannot determine size of info type " << g_info_type_to_string(info_type); + throw UnsupportedGIType(message.str()); + } + break; + } + case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_VOID: + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + case GI_TYPE_TAG_GHASH: + case GI_TYPE_TAG_ERROR: + return sizeof(void *); + } + return size; +} + +gsize get_type_tag_size(GITypeTag type_tag) { + switch (type_tag) { + case GI_TYPE_TAG_BOOLEAN: + return sizeof(gboolean); + case GI_TYPE_TAG_INT8: + case GI_TYPE_TAG_UINT8: + return sizeof(gint8); + case GI_TYPE_TAG_INT16: + case GI_TYPE_TAG_UINT16: + return sizeof(gint16); + case GI_TYPE_TAG_INT32: + case GI_TYPE_TAG_UINT32: + return sizeof(gint32); + case GI_TYPE_TAG_INT64: + case GI_TYPE_TAG_UINT64: + return sizeof(gint64); + case GI_TYPE_TAG_FLOAT: + return sizeof(gfloat); + case GI_TYPE_TAG_DOUBLE: + return sizeof(gdouble); + case GI_TYPE_TAG_GTYPE: + return sizeof(GType); + case GI_TYPE_TAG_UNICHAR: + return sizeof(gunichar); + case GI_TYPE_TAG_VOID: + case GI_TYPE_TAG_UTF8: + case GI_TYPE_TAG_FILENAME: + case GI_TYPE_TAG_ARRAY: + case GI_TYPE_TAG_INTERFACE: + case GI_TYPE_TAG_GLIST: + case GI_TYPE_TAG_GSLIST: + case GI_TYPE_TAG_GHASH: + case GI_TYPE_TAG_ERROR: + default: + stringstream message; + message << "unable to determine the size of " << g_type_tag_to_string(type_tag); + throw UnsupportedGIType(message.str()); + } +} + +} // namespace gir diff --git a/src/type.h b/src/type.h new file mode 100644 index 0000000..c10f0ba --- /dev/null +++ b/src/type.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace gir { + +gsize get_type_size(GITypeInfo *type_info); +gsize get_type_tag_size(GITypeTag type_tag); + +} // namespace gir diff --git a/src/types/function.cpp b/src/types/function.cpp index 438cb28..1cd8394 100644 --- a/src/types/function.cpp +++ b/src/types/function.cpp @@ -69,11 +69,13 @@ GIArgument GIRFunction::call_native(GIFunctionInfo *function_info, Args &args) { GIArgument return_value; GError *error = nullptr; + auto in_args = args.get_in_args(); + auto out_args = args.get_out_args(); g_function_info_invoke(function_info, - args.in.data(), - args.in.size(), - args.out.data(), - args.out.size(), + in_args.data(), + in_args.size(), + out_args.data(), + out_args.size(), &return_value, &error); @@ -141,26 +143,29 @@ Local GIRFunction::js_return_value_from_native_call(GIFunctionInfo *funct GITypeInfo return_type_info; g_callable_info_load_return_type(function_info, &return_type_info); + auto out_args = args.get_out_args(); + auto all_args = args.get_all_args(); + // if the function's metadata says to skip the return value (meaning the // return value is only useful in C) or the return value is void, then we can // skip the return value when determining what should be returned from native // to JS. bool skip_return_value = g_callable_info_skip_return(function_info) || g_type_info_get_tag(&return_type_info) == GI_TYPE_TAG_VOID; - int number_of_return_values = skip_return_value ? args.out.size() : args.out.size() + 1; + int number_of_return_values = skip_return_value ? out_args.size() : out_args.size() + 1; Local js_result_array = Nan::New(number_of_return_values); // if we should NOT skip the native return value, then we should convert it to // JS and set it in position 0 of the returned value array if (!skip_return_value) { - Local js_return_value = Args::from_g_type(&native_call_result, &return_type_info, 0); + Local js_return_value = Args::from_g_type(&native_call_result, &return_type_info); js_result_array->Set(0, js_return_value); } // We need to handle OUT arguments from the native call. // If we had some out args then - // foreach native argument, if it's an our arg, grab the next out arg from + // foreach native argument, if it's an out arg, grab the next out arg from // args and add it to the next position in the js_result_array. The code here // is a bit confusing because `g_callable_info_load_arg(info, index)` requires // the argument index where the index is relative to ALL arguments where as @@ -170,16 +175,22 @@ Local GIRFunction::js_return_value_from_native_call(GIFunctionInfo *funct // offset the out args by 1 i.e. // [return_value, out-arg-1, out-arg-2, ...] int next_out_arg_pos = 0; - if (args.out.size() > 0) { + if (out_args.size() > 0) { for (int i = 0; i < g_callable_info_get_n_args(function_info); i++) { GIArgInfo argument_info; g_callable_info_load_arg(function_info, i, &argument_info); GIDirection argument_direction = g_arg_info_get_direction(&argument_info); - if (argument_direction == GI_DIRECTION_OUT) { + if (argument_direction == GI_DIRECTION_OUT || argument_direction == GI_DIRECTION_INOUT) { GITypeInfo out_arg_type_info; g_arg_info_load_type(&argument_info, &out_arg_type_info); + int length = Args::get_g_type_array_length(function_info, + all_args, + &out_args[next_out_arg_pos], + &out_arg_type_info); js_result_array->Set(js_results_array_pos, - Args::from_g_type(&args.out[next_out_arg_pos], &out_arg_type_info, 0)); + Args::from_g_type(static_cast(out_args[next_out_arg_pos].v_pointer), + &out_arg_type_info, + length)); next_out_arg_pos += 1; js_results_array_pos += 1; } diff --git a/src/types/param_spec.cpp b/src/types/param_spec.cpp index 7188fda..612af19 100644 --- a/src/types/param_spec.cpp +++ b/src/types/param_spec.cpp @@ -2,7 +2,6 @@ #include "types/param_spec.h" - namespace gir { Nan::Persistent GIRParamSpec::instance_constructor; diff --git a/src/types/struct.cpp b/src/types/struct.cpp index 0d63028..4064070 100644 --- a/src/types/struct.cpp +++ b/src/types/struct.cpp @@ -71,7 +71,7 @@ Local GIRStruct::prepare(GIStructInfo *info) { // to the JS function (constructor) Local object_template = Nan::New(GIRStruct::constructor, struct_info_extern); GIRStruct::prepared_js_classes.insert( - make_pair(g_registered_type_info_get_g_type(info), PersistentFunctionTemplate(object_template))); + make_pair(g_registered_type_info_get_g_type(info), PersistentFunctionTemplate(object_template))); object_template->SetClassName(Nan::New(name).ToLocalChecked()); @@ -142,7 +142,6 @@ GIRInfoUniquePtr GIRStruct::find_native_constructor(GIStructInfo *struct_info) { NAN_METHOD(GIRStruct::constructor) { Local struct_info_extern = Local::Cast(info.Data()); GIStructInfo *struct_info = (GIStructInfo *)struct_info_extern->Value(); - auto name = g_base_info_get_name(struct_info); GIRStruct *obj = new GIRStruct(); obj->struct_info = GIRInfoUniquePtr(struct_info); @@ -222,7 +221,7 @@ NAN_PROPERTY_GETTER(GIRStruct::property_get_handler) { // converty the native value to a JS value auto type_info = GIRInfoUniquePtr(g_field_info_get_type(field_info.get())); - Local res = Args::from_g_type(&native_field_value, type_info.get(), 0); + Local res = Args::from_g_type(&native_field_value, type_info.get()); info.GetReturnValue().Set(res); return; } diff --git a/src/types/struct.h b/src/types/struct.h index de61131..d2e252b 100644 --- a/src/types/struct.h +++ b/src/types/struct.h @@ -2,10 +2,10 @@ #include #include +#include #include #include #include -#include #include "util.h" namespace gir { diff --git a/src/util.h b/src/util.h index d1a1150..d95cfe9 100644 --- a/src/util.h +++ b/src/util.h @@ -67,6 +67,12 @@ vector extract_values(map &input_map) { } return retval; } + +template +bool instance_of(T *instance) { + return dynamic_cast

(instance) != nullptr; +} + } // namespace Util } // namespace gir diff --git a/src/values.cpp b/src/values.cpp index 956e146..ab2a9d9 100644 --- a/src/values.cpp +++ b/src/values.cpp @@ -6,6 +6,7 @@ #include #include "arguments.h" #include "exceptions.h" +#include "type.h" #include "types/object.h" #include "types/param_spec.h" #include "types/struct.h" @@ -201,6 +202,80 @@ GValue GIRValue::to_g_value(Local js_value, GType g_type) { return g_value; } +void *GIRValue::to_c_array(Local value, GITypeInfo *type_info) { + bool is_zero_terminated = g_type_info_is_zero_terminated(type_info); + + if (value->IsString()) { + Local string = value->ToString(); + const char *utf8_data = *Nan::Utf8String(string); + return g_strdup(utf8_data); + } + + if (!value->IsArray()) { + throw JSValueError("expected value to be an array"); + } + + Local array = Local::Cast(value->ToObject()); + int length = array->Length(); + + auto element_info = GIRInfoUniquePtr(g_type_info_get_param_type(type_info, 0)); + gsize element_size = get_type_size(element_info.get()); + + void *result = malloc(element_size * (length + (is_zero_terminated ? 1 : 0))); + + for (int i = 0; i < length; i++) { + auto value = array->Get(i); + GIArgument arg = Args::type_to_g_type(*element_info.get(), value); + void *pointer = (void *)((ulong)result + i * element_size); + memcpy(pointer, &arg, element_size); + } + + if (is_zero_terminated) { + void *pointer = (void *)((ulong)result + length * element_size); + memset(pointer, 0, element_size); + } + + return result; +} + +GArray *GIRValue::to_g_array(Local value, GITypeInfo *type_info) { + GArray* g_array = nullptr; + bool zero_terminated = g_type_info_is_zero_terminated(type_info); + + if (value->IsString()) { + Local string = value->ToString(); + int length = string->Length(); + + if (length == 0) { + return g_array_new(zero_terminated, true, sizeof(char)); + } + + const char *utf8_data = *Nan::Utf8String(string); + g_array = g_array_sized_new (zero_terminated, false, sizeof (char), length); + return g_array_append_vals(g_array, utf8_data, length); + + } else if (value->IsArray ()) { + auto array = Local::Cast (value->ToObject ()); + int length = array->Length (); + + auto element_info = GIRInfoUniquePtr(g_type_info_get_param_type(type_info, 0)); + gsize element_size = get_type_size(element_info.get()); + + // FIXME this is so wrong + g_array = g_array_sized_new(zero_terminated, true, element_size, length); + + for (int i = 0; i < length; i++) { + auto value = array->Get(i); + GIArgument arg = Args::type_to_g_type(*element_info.get(), value); + g_array_append_val(g_array, arg); + } + } else { + throw JSValueError("expected an array"); + } + + return g_array; +} + GType GIRValue::guess_type(Handle value) { if (value->IsString()) { return G_TYPE_STRING; diff --git a/src/values.h b/src/values.h index b06299a..4511e0b 100644 --- a/src/values.h +++ b/src/values.h @@ -13,6 +13,9 @@ class GIRValue { static GValue to_g_value(Local value, GType g_type); static Local from_g_value(const GValue *v, GITypeInfo *type_info); + static void *to_c_array(Local value, GITypeInfo *type_info); + static GArray *to_g_array(Local value, GITypeInfo *type_info); + private: static GType guess_type(Local value); };