Я пытаюсь выяснить, как получить доступ к массиву JavaScript во встроенной версии v8 через функцию С++.
Пример JavaScript, вызывающий мою пользовательскую функцию:
my_func([1, 2, 3]);
Если я явно создаю типизированный массив, такой как Uint8Array
(например, my_func(new Uint8Array([1,2,3])
), я смогу получить к нему доступ из объекта v8::FunctionCallbackInfo
следующим образом:
void my_func(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Uint8Array> arr = info[0].As<v8::Uint8Array>();
// can access the raw buffer or specific elements via `arr` now
}
Однако я не могу найти аналогичный подход для простого объекта v8::Array
, а документация/пример кода по этой теме отсутствуют. Может ли кто-нибудь указать мне правильное направление, как это сделать?
Я попробовал несколько вариантов этого, но это вызывает ошибку:
v8::Local<v8::Array> arr = info[0].As<v8::Array>();
v8::MaybeLocal<v8::Number> n;
arr->Get(ctx, 0).Cast<v8::Number>(n); // fault is triggered here
auto x = n.ToLocalChecked();
Вина:
#
# Fatal error in ../../src/api/api-inl.h, line 171
# Debug check failed: v8::internal::ValueHelper::IsEmpty(that) || IsJSReceiver(v8::internal::Tagged<v8::internal::Object>( v8::internal::ValueHelper::ValueAsAddress(that))).
#
Я также попробовал два верхних ответа на связанный вопрос, который был успешно выполнен, но дал неверные данные. Я подозреваю, что это связано с внутренним различием v8 между значениями, поступающими из JavaScript, и данными, созданными в C++, но я недостаточно знаком с v8, чтобы сказать наверняка.
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(info[0]);
auto i = array->Get(ctx, 0).ToLocalChecked();
// `i` looks like a truncated pointer from a quick glance, definitely not the correct value
v8::Handle<v8::Object> obj = info[0]->ToObject(ctx).ToLocalChecked();
v8::Local<v8::Value> element = obj->Get(ctx, 0).ToLocalChecked();
auto i = element->ToUint32(ctx).ToLocalChecked();
// this behaves the same as the previous snippet
Это может показаться очевидным, но может быть полезно попытаться понять, что вам нужно, а не просто слепо пробовать фрагменты, которые вы видели где-то еще :-)
В данном случае релевантными понятиями являются:
(1) Проверка пустых Local
. Большинство операций с объектами JavaScript могут вызывать выдачу, и в этом случае они не возвращают никакого значения (поскольку вместо возврата значения они выбрасывают исключение); API V8 делает это очевидным, используя тип MaybeLocal
: всякий раз, когда у вас есть один из них, вы должны проверить наличие исключения или, наоборот: проверить, действительно ли у вас есть значение. В некоторых особых случаях, когда вы можете это гарантировать (обычно это означает: поскольку вы уже проверяли ранее каким-либо другим способом), вы можете использовать помощник ToLocalChecked
. Часть имени «Проверено» означает: если MaybeLocal
действительно был пустым (из-за исключения), произойдет сбой. Поэтому обычно, чтобы избежать сбоя, вы должны использовать шаблон:
v8::MaybeLocal<ResultType> maybe = SomeOperationThatCanThrow();
v8::Local<ResultType> result;
if (!maybe.ToLocal(&result)) {
// Handle error...
return;
}
// If `ToLocal` returned `true`, `result` is now populated.
Use(result);
(2) Проверка типов и приведение типов. Большинство операций с объектами JavaScript могут возвращать значение произвольного типа, поскольку JavaScript — нетипизированный язык. Таким образом, API V8 предоставляет общие типы, конкретные типы и средства для проверки типа значения и преобразования его в более конкретный тип: v8::Value
— это практически что угодно. Различные IsFoo()
методы могут проверить его тип; если вы не можете гарантировать, что значение имеет определенный тип, вам нужно будет проверить его тип перед его приведением. Для приведения v8::Local
имеет статическую функцию Cast<TargetType>
и эквивалентный нестатический помощник As<TargetType>
, что может быть более удобно. Их объединяет то, что они действительны только в том случае, если значение имеет правильный тип; с V8_ENABLE_CHECKS
в противном случае произойдет сбой, без него они просто дадут вам неверный дескриптор, который, скорее всего, приведет к сбою при одном из следующих нескольких действий, которые вы с ним сделаете. Итак, типичная схема:
v8::Local<v8::Value> value = SomethingThatReturnsAValue();
if (value->IsObject()) {
v8::Local<v8::Object> obj1 = v8::Local<v8::Object>::Cast(value);
v8::Local<v8::Object> obj2 = value.As<v8::Object>(); // Equivalent.
} else {
// Handle the case where `value` isn't an Object; perhaps by
// checking for some other type, or by performing a to-object
// conversion.
...
}
Теперь, соединив оба этих фактора вместе, вы, скорее всего, захотите что-то вроде:
if (!info[0]->IsArray()) {
// TODO: Handle this somehow.
return;
}
v8::Local<v8::Array> arr = info[0].As<v8::Array>();
v8::MaybeLocal<v8::Value> maybe_element = arr->Get(ctx, 0);
v8::Local<v8::Value> element;
if (!maybe_element.ToLocal(&element)) {
// TODO: Handle this somehow.
return;
}
if (!element->IsNumber()) {
// TODO: Handle this somehow.
return;
}
v8::Local<v8::Number> number = element.As<v8::Number>();
std::cout << "The number is " << number->Value() << std::endl;
И если вы думаете: «Нет, все эти проверки выглядят сложными, они мне не нужны все?», попробуйте вызвать функцию следующим образом:
my_func("");
my_func([]);
my_func(["what now?"]);
let nasty = [];
Object.defineProperty(nasty, 0, {get: () => { throw "not today;" }});
my_func(nasty);
(также предпоследняя строка последнего фрагмента кода C++ должна быть element.As<v8::Number>()
)
потрясающе, спасибо! в моем реальном коде у меня присутствовал этот материал для обработки ошибок (но приятно подтвердить, что я делал эту часть правильно), недостающим элементом, я думаю, было преобразование с помощью
As
для изменения v8::Value на v8::Number.