问题
This post is about exposing C++ objects to the v8 javascript engine. To attach a C++ object to a javascript object, I make use of the GetInternalField()
and External
APIs. Before you can set or get any internal field, you have to call SetInternalFieldCount()
on the corresponding ObjectTemplate
. Since I want to expose a constructor function to the JS, I created a FunctionTemplate
, set a C++ function that attache the native object to the JS object to that template, and finally SetInternalCount()
on the InstanceTemplate()
of that function template. Too much words for the description, here is what I did:
struct Point {
int x, y;
Local<FunctionTemplate> CreatePointContext(Isolate* isolate) {
Local<FunctionTemplate> constructor = FunctionTemplate::New(isolate, &ConstructorCallback);
constructor->InstanceTemplate()->SetInternalFieldCount(1); // I set internal field count here.
constructor->SetClassName(String::NewFromUtf8(isolate, "Point", NewStringType::kInternalized).ToLocalChecked());
auto prototype_t = constructor->PrototypeTemplate();
prototype_t->SetAccessor(String::NewFromUtf8(isolate, "x", NewStringType::kInternalized).ToLocalChecked(),
XGetterCallback);
return constructor;
};
// This callback is bound to the constructor to attach a C++ Point instance to js object.
static void ConstructorCallback(const FunctionCallbackInfo<Value>& args) {
auto isolate = args.GetIsolate();
Local<External> external = External::New(isolate, new Point);
args.Holder()->SetInternalField(0, external);
}
// This callback retrieves the C++ object and extract its 'x' field.
static void XGetterCallback(Local<String> property, const PropertyCallbackInfo<Value>& info) {
auto external = Local<External>::Cast(info.Holder()->GetInternalField(0)); // This line triggers an out-of-bound error.
auto point = reinterpret_cast<Point*>(external->Value());
info.GetReturnValue().Set(static_cast< double>(point->x));
}
// This function creates a context that install the Point function template.
Local<Context> CreatePointContext(Isolate* isolate) {
auto global = ObjectTemplate::New(isolate);
auto point_ctor = Point::CreateClassTemplate(isolate);
global->Set(isolate, "Point", point_ctor);
return Context::New(isolate, nullptr, global);
}
When I tried to run the following JS code with the exposed C++ object, I got Internal field out of bounds
error.
var p = new Point();
p.x;
I wonder setting internal field count on the instance template of a function template has nothing to do with the object created by the new expression. If so, what is the correct way to set the internal field count of the object created by new
while exposing the constructor function to javascript? I want to achieve the following 2 things:
- In javascript, a
Point
function is avaible so we canvar p = new Point;
. - In C++ I can make sure the JS object has 1 internal field for our C++ Point to live in.
Edit: As @snek pointed out, I changed Holder()
to This()
and everything started to work. But later When I changed SetAccessor
to SetAccessorProperty
, it worked even with Holder.
Although the behaviour are very confusing, I think the major problem may not lie in the difference between Holder and This, but rather in SetAccessor and SetAccessorProperty. Why? Because in many docs I have read, Holder should be identical to This in most cases and I believe without using Signature and given that my testing js code is so simple (not with any magic property moving), in my case This should just be Holder.
Thus I decided to post another question about SetAccessor and SetAccessorProperty and leave this post as a reference.
For why I am so sure about in my case This() == Holder()
should hold, here are some old threads:
- https://groups.google.com/forum/#!topic/v8-users/fK9PBWxJxtQ
- https://groups.google.com/forum/#!topic/v8-users/Axf4hF_RfZo
And what does the docs say?
/**
* If the callback was created without a Signature, this is the same
* value as This(). If there is a signature, and the signature didn't match
* This() but one of its hidden prototypes, this will be the respective
* hidden prototype.
*
* Note that this is not the prototype of This() on which the accessor
* referencing this callback was found (which in V8 internally is often
* referred to as holder [sic]).
*/
V8_INLINE Local<Object> Holder() const;
Note in my code there is not Signature, literally. So This and Holder should make no difference, but with SetAccessor
, they made a difference.
来源:https://stackoverflow.com/questions/57675376/setinternalfieldcount-on-constructor-instancetemplate-did-not-work-for-the-in