Skip to content

Instantly share code, notes, and snippets.

@Chaircrusher
Last active January 9, 2017 21:38
Show Gist options
  • Save Chaircrusher/48d53ee78567ca213f7a858e91e4771d to your computer and use it in GitHub Desktop.
Save Chaircrusher/48d53ee78567ca213f7a858e91e4771d to your computer and use it in GitHub Desktop.
cut down example of making a C++ 'object' for V8
// Example Class instantiation
// by Kent Williams [email protected]
//
#include <iostream>
#include <cstring>
#include <memory>
#include <v8.h>
#include <libplatform/libplatform.h>
// template for TestClass
v8::Eternal<v8::ObjectTemplate> TestClassGlobal;
long long TestClassIdentifier(0);
// a sort-of destructor in v8 land
void TestClassMakeWeak(const v8::WeakCallbackInfo<char> &data) {
// the private object is the doc root handle, which doesn't need
// deleting.
//
delete [] data.GetParameter();
}
// SetTestClassData -- set the private data in the class methods
static void SetTestClassData(v8::Isolate *isolate,v8::Local<v8::Object> obj, char *s) {
v8::HandleScope handle_scope(isolate);
#if 0
// I think anything wrapped with v8::External needs to be
// explicitly freed.
// get the saved field
v8::Local<v8::Value> oldValue = obj->GetInternalField(0);
v8::Local<v8::External> Extern = v8::Local<v8::External>::Cast(oldValue);
// get the raw pointer
void *oldval = Extern->Value();
// delete it
if(oldval)
delete [] static_cast<char *>(oldval);
#endif
obj->SetInternalField(0,v8::External::New(isolate,s));
obj->SetAlignedPointerInInternalField(1,&TestClassIdentifier);
v8::Persistent<v8::Object,v8::CopyablePersistentTraits<v8::Object>> pobj(isolate,obj);
pobj.SetWeak<char>(s,TestClassMakeWeak,v8::WeakCallbackType::kParameter);
}
// true if this object is an instance of the TestClass
bool IsTestClassObject(v8::Local<v8::Object> obj) {
int fieldcount = obj->InternalFieldCount();
const void *id = nullptr;
if(fieldcount >= 2)
id = obj->GetAlignedPointerFromInternalField(1);
return id == static_cast<void *>(&TestClassIdentifier);
}
//
// Constructor function to call from C++ to get a new instance.
// the internal field is set to null
v8::Local<v8::Object> NewTestClass(v8::Isolate *isolate,v8::Local<v8::Context> context) {
v8::EscapableHandleScope handle_scope(isolate);
v8::Local<v8::Object> retobj = TestClassGlobal.Get(isolate)->NewInstance(context).ToLocalChecked();
// does this do the right thing?
retobj->SetAlignedPointerInInternalField(0,nullptr);
retobj->SetAlignedPointerInInternalField(1,static_cast<void *>(&TestClassIdentifier));
return handle_scope.Escape(retobj);
}
//
// given a string argument, set it as the internal value
void SetPrivateString(const v8::FunctionCallbackInfo<v8::Value> &args) {
v8::Isolate *isolate(args.GetIsolate());
v8::HandleScope handle_scope(isolate);
args.GetReturnValue().Set(v8::Null(isolate));
if(!IsTestClassObject(args.This()))
return;
if(args.Length() < 1 || !args[0]->IsString())
return;
v8::String::Utf8Value tmp(args[0]->ToString());
// allocate string
const char *val = *tmp ? *tmp : "";
char *setval = new char [strlen(val)+1];
strcpy(setval,val);
// set the internal value
SetTestClassData(isolate,args.This(),setval);
args.GetReturnValue().Set(v8::True(isolate));
}
// retrieve string and return it.
void GetPrivateString(const v8::FunctionCallbackInfo<v8::Value> &args) {
v8::Isolate *isolate(args.GetIsolate());
v8::HandleScope handle_scope(isolate);
args.GetReturnValue().Set(v8::Null(isolate));
if(!IsTestClassObject(args.This()))
return;
v8::Local<v8::External> Extern =
v8::Local<v8::External>::Cast(args.This()->GetInternalField(0));
char *rval = static_cast<char *>(Extern->Value());
v8::Local<v8::String> s = v8::String::NewFromUtf8(isolate,rval);
args.GetReturnValue().Set(s);
}
// to list object methods
struct v8FunctionDescriptor {
const char *name;
v8::FunctionCallback func;
};
// list of object methods
static v8FunctionDescriptor TestClassMethods[] = {
// list of name/functions
{ "SetPrivateString", SetPrivateString },
{ "GetPrivateString", GetPrivateString },
{ nullptr, nullptr },
};
// constructor called from JS to make
static void TestClassConstructor(const v8::FunctionCallbackInfo<v8::Value> &args) {
v8::Isolate *isolate(args.GetIsolate());
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
//
// get upset if this isn't a constructor call
if(!args.IsConstructCall()) {
isolate->ThrowException(v8::String::NewFromUtf8(isolate,
"Cannot call constructor as function"));
}
v8::Local<v8::Object> holder = args.Holder();
v8::Local<v8::Object> This = args.This();
v8::Local<v8::Value> proto = holder->GetPrototype();
if(proto->IsObject())
This->SetPrototype(context,proto);
//
// private data
char *privateString = new char[512];
std::strcpy(privateString,"Hello From TestClass");
SetTestClassData(isolate,This,privateString);
args.GetReturnValue().Set(This);
}
void init_TestClass(v8::Isolate *isolate,
v8::Local<v8::Context> &context) {
v8::HandleScope handle_scope(isolate);
// name of 'class'
v8::Local<v8::String> name =
v8::String::NewFromUtf8(isolate, "TestClass",
v8::NewStringType::kNormal).ToLocalChecked();
// object template for the class
v8::Local<v8::ObjectTemplate> instance_template = v8::ObjectTemplate::New(isolate);
// tell it to allocate 2 internal fields
instance_template->SetInternalFieldCount(2);
// give it the constructor to call
instance_template->SetCallAsFunctionHandler(TestClassConstructor);
// save Eternal pointer in order to call NewInstance from C++
TestClassGlobal.Set(isolate,instance_template);
// add functions to object template
for(unsigned i = 0; TestClassMethods[i].name != nullptr; ++i) {
v8::Local<v8::FunctionTemplate> ft =
v8::FunctionTemplate::New(isolate, TestClassMethods[i].func);
ft->SetClassName(name);
v8::Local<v8::String> curName =
v8::String::NewFromUtf8(isolate, TestClassMethods[i].name,
v8::NewStringType::kNormal).ToLocalChecked();
// testClass_PrototypeTemplate->Set(curName, ft->GetFunction(context).ToLocalChecked());
instance_template->Set(curName, ft);
}
v8::Local<v8::Object> instance = instance_template->NewInstance(context).ToLocalChecked();
context->Global()->Set(context,name, instance).FromJust();
}
// Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}
void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch);
v8::Local<v8::Context> CreateShellContext(v8::Isolate* isolate);
const char TestScript[] =
"print('1. this is a test');\n"
"var x = new TestClass;\n"
"x.SetPrivateString('This Is A test');\n"
"print('2. ' + x.GetPrivateString());\n"
"x.SetPrivateString('This Is A test');\n"
"print('3. ' + x.GetPrivateString());\n";
int main(int argc, char **argv) {
// Initialize V8.
v8::V8::InitializeICUDefaultLocation(argv[0]);
v8::V8::InitializeExternalStartupData(argv[0]);
v8::Platform* platform = v8::platform::CreateDefaultPlatform();
v8::V8::InitializePlatform(platform);
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
v8::HandleScope handle_scope(isolate);
// create context with global template
v8::Local<v8::Context> context = CreateShellContext(isolate);
if (context.IsEmpty()) {
fprintf(stderr, "Error creating context\n");
return 1;
}
// Enter the context for compiling and running the hello world script.
v8::Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source =
v8::String::NewFromUtf8(isolate, TestScript,
v8::NewStringType::kNormal).ToLocalChecked();
v8::TryCatch try_catch(isolate);
v8::Local<v8::Script> script;
// Compile the source code.
if(!v8::Script::Compile(context, source).ToLocal(&script)) {
ReportException(isolate, &try_catch);
}
// Run the script to get the result.
v8::MaybeLocal<v8::Value> result = script->Run(context);
if(!result.IsEmpty()) {
// Convert the result to an UTF8 string and print it.
v8::String::Utf8Value utf8(result.ToLocalChecked());
printf("%s\n", *utf8);
} else {
ReportException(isolate, &try_catch);
}
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete platform;
delete create_params.array_buffer_allocator;
return 0;
}
// The callback that is invoked by v8 whenever the JavaScript 'print'
// function is called. Prints its arguments on stdout separated by
// spaces and ending with a newline.
void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate *isolate(args.GetIsolate());
bool first = true;
for (int i = 0; i < args.Length(); i++) {
v8::HandleScope handle_scope(isolate);
if (first) {
first = false;
} else {
printf(" ");
}
v8::String::Utf8Value str(args[i]);
const char* cstr = ToCString(str);
printf("%s", cstr);
}
printf("\n");
fflush(stdout);
args.GetReturnValue().Set(v8::True(isolate));
}
v8::Local<v8::Context> CreateShellContext(v8::Isolate* isolate) {
// Create a template for the global object.
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
// Bind the global 'print' function to the C++ Print callback.
global->Set(
v8::String::NewFromUtf8(isolate, "print", v8::NewStringType::kNormal)
.ToLocalChecked(),
v8::FunctionTemplate::New(isolate, Print));
v8::Local<v8::Context> rval = v8::Context::New(isolate, NULL, global);
// add our test class
init_TestClass(isolate,rval);
return rval;
}
void ReportException(v8::Isolate* isolate, v8::TryCatch* try_catch) {
v8::HandleScope handle_scope(isolate);
v8::String::Utf8Value exception(try_catch->Exception());
const char* exception_string = ToCString(exception);
v8::Local<v8::Message> message = try_catch->Message();
if (message.IsEmpty()) {
// V8 didn't provide any extra information about this error; just
// print the exception.
fprintf(stderr, "%s\n", exception_string);
} else {
// Print (filename):(line number): (message).
v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName());
v8::Local<v8::Context> context(isolate->GetCurrentContext());
const char* filename_string = ToCString(filename);
int linenum = message->GetLineNumber(context).FromJust();
fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
// Print line of source code.
v8::String::Utf8Value sourceline(
message->GetSourceLine(context).ToLocalChecked());
const char* sourceline_string = ToCString(sourceline);
fprintf(stderr, "%s\n", sourceline_string);
// Print wavy underline (GetUnderline is deprecated).
int start = message->GetStartColumn(context).FromJust();
for (int i = 0; i < start; i++) {
fprintf(stderr, " ");
}
int end = message->GetEndColumn(context).FromJust();
for (int i = start; i < end; i++) {
fprintf(stderr, "^");
}
fprintf(stderr, "\n");
v8::Local<v8::Value> stack_trace_string;
if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) &&
stack_trace_string->IsString() &&
v8::Local<v8::String>::Cast(stack_trace_string)->Length() > 0) {
v8::String::Utf8Value stack_trace(stack_trace_string);
const char* stack_trace_string = ToCString(stack_trace);
fprintf(stderr, "%s\n", stack_trace_string);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment