Addon API (Native)
Technical guide for building native C++ addons for Novadesk.
Table of Contents
This guide covers the technical details of the Novadesk Addon SDK, used to build native x64 DLLs that integrate with the Novadesk JavaScript engine.
Architecture
Novadesk addons are engine-agnostic. Instead of linking against a specific JavaScript engine (like Duktape), addons interact with Novadesk via a provided Host API table. This ensures binary compatibility even if the underlying engine is updated.
C++ Entry Points
NOVADESK_ADDON_INIT
The primary entry point for any addon. It is called by Novadesk when addon.load() is executed.
#include <NovadeskAPI/novadesk_addon.h>
NOVADESK_ADDON_INIT(ctx, hMsgWnd, host) {
// ctx: Opaque handle to the JS context
// hMsgWnd: Window handle for thread-safe dispatching
// host: Pointer to the Host API (Never use this directly, use novadesk::Addon wrapper)
novadesk::Addon addon(ctx, host);
addon.RegisterString("version", "1.0.0");
}NOVADESK_ADDON_UNLOAD
Optional hook called when the addon is manually unloaded or when Novadesk reloads scripts.
NOVADESK_ADDON_UNLOAD() {
// Cleanup your background threads, global memory, etc.
}Host API Functions
The novadesk::Addon helper class provides a convenient wrapper around the low-level Host API.
Registering Data
Native addons export functionality by adding properties to the object returned by addon.load(). Use the following methods on the novadesk::Addon instance.
RegisterString(name, value)
Registers a static string property.
addon.RegisterString("version", "1.2.0");
addon.RegisterString("author", "Novadesk Team");RegisterNumber(name, value)
Registers a numeric property (supports double, int, float).
addon.RegisterNumber("maxConnections", 100);
addon.RegisterNumber("pi", 3.14159);RegisterBool(name, value)
Registers a boolean property.
addon.RegisterBool("isEnabled", true);RegisterArray(name, values)
Registers an array of either strings or numbers.
// String array
addon.RegisterArray("tags", {"native", "performance", "win32"});
// Number array
addon.RegisterArray("levels", {1.0, 5.5, 10.0});RegisterObject(name, callback)
Creates a nested object to organize your API. The callback receives a new novadesk::Addon instance representing the sub-object.
addon.RegisterObject("utils", [](novadesk::Addon& utils) {
utils.RegisterString("status", "ok");
utils.RegisterNumber("id", 12345);
});Registering Functions
Functions are registered with a name, a C++ lambda or function pointer, and the number of expected arguments (nargs).
addon.RegisterFunction("hello", [](novadesk_context ctx) -> int {
// 'ctx' is the JS context handle
return 0; // Returning 0 means no value is returned to JS
}, 0);Manual Stack Management
While high-level registration handles most cases, you can manually push values onto the JavaScript stack using the host pointer or the Addon helper.
PushString(value)
Pushes a null-terminated string onto the stack.
host->PushString(ctx, "Directly pushed string");PushNumber(value)
Pushes a numeric value.
host->PushNumber(ctx, 3.14);PushBool(value)
Pushes a boolean value (as 1 or 0).
host->PushBool(ctx, 1); // truePushNull()
Pushes a null value.
host->PushNull(ctx);PushObject()
Pushes a new, empty JavaScript object.
host->PushObject(ctx);Handling Arguments (In Detail)
When a JavaScript function calls your native addon, use the Addon utility methods to safely retrieve arguments from the stack.
GetNumber(index)
Retrieves a numeric value at the specified stack index (0-based).
// JS: addon.sum(10, 20)
addon.RegisterFunction("sum", [host](novadesk_context ctx) -> int {
novadesk::Addon helper(ctx, host);
double a = helper.GetNumber(0); // 10
double b = helper.GetNumber(1); // 20
// Return the result
host->PushNumber(ctx, a + b);
return 1; // 1 means we pushed one return value
}, 2);GetString(index)
Retrieves a string value.
// JS: addon.greet("World")
addon.RegisterFunction("greet", [host](novadesk_context ctx) -> int {
novadesk::Addon helper(ctx, host);
const char* name = helper.GetString(0);
std::string response = "Hello, " + std::string(name) + "!";
host->PushString(ctx, response.c_str());
return 1;
}, 1);GetBool(index)
Retrieves a boolean value as a bool.
// JS: addon.test(true)
addon.RegisterFunction("test", [host](novadesk_context ctx) -> int {
novadesk::Addon helper(ctx, host);
bool val = helper.GetBool(0); // true
return 0;
}, 1);Type Safety & Validation
Always validate arguments before using them to prevent addon crashes or engine errors.
IsNumber(index)
Returns true if the argument at the specified index is a numeric type.
IsString(index)
Returns true if the argument is a string.
IsBool(index)
Returns true if the argument is a boolean.
IsFunction(index)
Returns true if the argument is a JavaScript function (callback).
IsObject(index)
Returns true if the argument is a JavaScript object.
IsNull(index)
Returns true if the argument is null or undefined.
Stack & Error Control
These methods provide direct control over the JavaScript stack state and error reporting.
GetTop()
Returns the number of items currently on the stack.
int count = helper.GetTop();Pop()
Removes the top-most item from the stack.
helper.Pop();PopN(n)
Removes n items from the stack.
helper.PopN(3); // Remove top 3 itemsThrowError(message)
Improves error reporting by throwing a native JavaScript error. This immediately interrupts C++ execution of the function.
if (idx < 0) {
helper.ThrowError("Index cannot be negative!");
return 0;
}Example: Robust Math
addon.RegisterFunction("multiply", [host](novadesk_context ctx) -> int {
novadesk::Addon helper(ctx, host);
if (!helper.IsNumber(0) || !helper.IsNumber(1)) {
helper.ThrowError("multiply() requires two numbers!");
return 0;
}
host->PushNumber(ctx, helper.GetNumber(0) * helper.GetNumber(1));
return 1;
}, 2);JavaScript Callbacks (JsFunction)
The JsFunction class allows you to capture a JavaScript function passed as an argument and call it later from your C++ code (on the main thread).
JsFunction(ctx, host, index)
Captures a function from the specified stack index. Use IsFunction(index) first to ensure validity.
addon.RegisterFunction("onComplete", [host](novadesk_context ctx) -> int {
novadesk::Addon helper(ctx, host);
// Capture the function at index 0
novadesk::JsFunction callback(ctx, host, 0);
if (callback.IsValid()) {
callback.Call("Operation Successful!"); // Call with string
callback.Call(42.0); // Call with number
callback.Call(); // Call with no args
}
return 0;
}, 1);Thread Safety & Callbacks
Native addons often perform work in the background. You must never touch novadesk_context or call Host API functions from a background thread.
Using the Dispatcher
The Dispatcher allows you to send a task from a background thread to the Main Thread, where it is safe to interact with JavaScript.
// In INIT:
auto dispatcher = new novadesk::Dispatcher(hMsgWnd);
// In a thread:
std::thread([dispatcher]() {
// Long running work...
double result = 100.0;
dispatcher->Dispatch([](void* data) {
// THIS CODE RUNS ON THE MAIN THREAD
// Safe to use Host API here!
});
}).detach();Low-Level Host API (C-Compatible)
For developers not using the C++ helper classes, all operations can be performed directly via the NovadeskHostAPI structure passed to NovadeskAddonInit.
Object Creation (Raw)
To create an object manually:
host->RegisterObjectStart(ctx, "myObject");
host->RegisterString(ctx, "innerProp", "value");
host->RegisterObjectEnd(ctx, "myObject");Array Registration (Raw)
To register arrays using raw pointers:
const char* tags[] = { "alpha", "beta", "gamma" };
host->RegisterArrayString(ctx, "tags", tags, 3);
const double values[] = { 1.1, 2.2, 3.3 };
host->RegisterArrayNumber(ctx, "values", values, 3);Callbacks (Raw)
The low-level API uses opaque pointers for JS functions:
void* fnPtr = host->JsGetFunctionPtr(ctx, 0); // Capture function at index 0
if (fnPtr) {
host->PushString(ctx, "Hello from raw C++");
host->JsCallFunction(ctx, fnPtr, 1); // Call with 1 argument
}Full Example: System Utilities
#include <NovadeskAPI/novadesk_addon.h>
NOVADESK_ADDON_INIT(ctx, hMsgWnd, host) {
novadesk::Addon addon(ctx, host);
// 1. Simple Property
addon.RegisterString("author", "Novadesk Team");
// 2. Nested Utility Object
addon.RegisterObject("math", [host](novadesk::Addon& math) {
math.RegisterFunction("add", [host](novadesk_context ctx) -> int {
novadesk::Addon h(ctx, host);
host->PushNumber(ctx, h.GetNumber(0) + h.GetNumber(1));
return 1;
}, 2);
});
// 3. Status Check with Type Validation
addon.RegisterFunction("checkStatus", [host](novadesk_context ctx) -> int {
novadesk::Addon h(ctx, host);
if (h.IsString(0)) {
host->PushBool(ctx, 1);
} else {
host->PushBool(ctx, 0);
}
return 1;
}, 1);
}