The whole point of embedding is extensibility: you host a scripting environment and expose your
application's capabilities to it. In V8 you do that with templates. A
FunctionTemplate is, in the guide's words, "the blueprint for a single function",
and you can "associate a C++ callback with a function template which is called when the JavaScript
function instance is invoked."
add(3,4)Every C++ function you expose has this exact shape. Everything — arguments in, result out, the
isolate — arrives through one parameter, args:
void AddCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
// Inputs: args.Length() = how many, args[i] = the i-th (a Local<Value>)
if (args.Length() < 2) return;
double a = args[0].As<v8::Number>()->Value();
double b = args[1].As<v8::Number>()->Value();
// Output: hand a JS value back to the caller
args.GetReturnValue().Set(v8::Number::New(isolate, a + b));
}
| Piece | Role |
|---|---|
| FunctionCallbackInfo<Value>& args | The one bundle V8 passes in: arguments, the receiver (this), the isolate, and the return-value slot. |
| args.GetIsolate() | The current isolate — you need it to create new JS values. |
| args.Length() / args[i] | Argument count and each argument as a Local<Value>. You convert/check types yourself. |
| args.GetReturnValue().Set(v) | The function's return value. Don't return a value in C++ — set it here. |
print from samples/shell.cc
follows the same shape — read an arg, do something, return nothing:
void LogCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
v8::HandleScope scope(isolate); // a callback should open its own scope
v8::String::Utf8Value str(isolate, args[0]);
printf("[js log] %s\n", *str);
}
Pattern from the Embedder's Guide & samples/shell.cc.
A callback isn't reachable from JS until you attach it to something. The usual move: build a
global ObjectTemplate, put your functions on it as FunctionTemplates, then create
the Context with that template — so JS sees add(...) and log(...) as
globals.
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
// 1. Blueprint for the global object
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
// 2. Attach native functions as global properties
global->Set(isolate, "add", v8::FunctionTemplate::New(isolate, AddCallback));
global->Set(isolate, "log", v8::FunctionTemplate::New(isolate, LogCallback));
// 3. Create the context using that global template
v8::Local<v8::Context> context = v8::Context::New(isolate, nullptr, global);
v8::Context::Scope context_scope(context);
// 4. Now JS can call them!
v8::Local<v8::String> src = v8::String::NewFromUtf8Literal(
isolate, "log('2 + 3 = ' + add(2, 3));");
v8::Script::Compile(context, src).ToLocalChecked()->Run(context).ToLocalChecked();
// prints: [js log] 2 + 3 = 5
FunctionTemplate/ObjectTemplate is a
blueprint, defined once. When you create a Context from the global template, V8 instantiates
it into a live global object. This separation lets you set up the shape of your API once and stamp
out fresh, isolated instances per Context — the same mechanism Chrome uses to give every frame its
own clean globals.
Take your hello_world.cc from Lesson 4, add the two callbacks above the
main function, replace the context creation with the template-wiring block, and swap the
script for one that calls add and log. Recompile with the same
command from Lesson 4. You'll have a host whose JavaScript can reach into your C++.
fs.readFile, console.log,
and every built-in are exactly this pattern at scale: C++ functions exposed to JS via templates,
reading args and setting return values. You've now seen the atom the whole thing is
built from.
HandleScope at the top of a callback like LogCallback?samples/process.cc pattern? Want to throw a JS
exception from C++, or read a JS object's properties inside a callback? Just ask — e.g. "Show me
how to wrap a C++ class as a JS object," or "How do I validate arg types and throw a TypeError?"
This was the roadmap, but mastery comes from building. Natural next steps your teacher can spin up
on request: wrapping a C++ class as a JS object (process.cc); building a tiny REPL like
shell.cc; a deep-dive on TurboFan's sea-of-nodes IR; or testing your understanding in the
v8-users community (see
RESOURCES.md). Tell me what you want to chase.