Skip to content

Addon API (Native) ​

Technical guide for building native C++ addons. Load them with the addon module 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.

The macro also exports a NovadeskAddonApiVersion() function automatically. Novadesk uses this to verify that the addon was compiled against a compatible API version before loading it.

cpp
#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.

cpp
NOVADESK_ADDON_UNLOAD() {
    // Cleanup your background threads, global memory, etc.
}

API Versioning ​

The header defines NOVADESK_ADDON_API_VERSION (currently 2). Novadesk checks this value against the version of the host at load time. If there is a mismatch, the addon is rejected with an error rather than crashing.

Constant Value Description
NOVADESK_ADDON_API_VERSION 2 The API version this header targets.

The NOVADESK_ADDON_INIT macro automatically exports NovadeskAddonApiVersion() which returns this constant. You do not need to write this yourself.

Recompile required

If you update to a new version of novadesk_addon.h that bumps NOVADESK_ADDON_API_VERSION, you must recompile and redistiribute your addon DLL. Old DLLs compiled against a lower version will be refused.

novadesk::App — Host App Info (API v2) ​

Added in API version 2. The novadesk::App helper class gives native addons read-only access to the same information exposed by the JavaScript app module — version strings, paths, and runtime flags.

Construct it from the host pointer passed to NOVADESK_ADDON_INIT:

cpp
NOVADESK_ADDON_INIT(ctx, hMsgWnd, host) {
    novadesk::App appInfo(host);

    if (!appInfo.IsAvailable()) {
        // Host is older than API v2 — app info unavailable
        return;
    }

    // Read host info
    const char* ver  = appInfo.GetNovadeskVersion(); // e.g. "0.9.11.0"
    const char* data = appInfo.GetAppDataPath();      // e.g. "C:/Users/Me/AppData/Roaming/Novadesk/"
    bool portable    = appInfo.IsPortable();
    bool firstRun    = appInfo.IsFirstRun();
}

App Methods ​

Method Returns Description
IsAvailable() bool true if the host supports API v2. Always check this before calling other methods.
GetProductVersion() const char* Widget product version (from meta.json when running inside nwm).
GetFileVersion() const char* Executable file version.
GetNovadeskVersion() const char* Hardcoded Novadesk engine version string.
GetAppDataPath() const char* Path to the Novadesk AppData folder (portable: exe dir; installed: %APPDATA%\Novadesk\).
GetSettingsFilePath() const char* Full path to settings.json.
GetLogPath() const char* Full path to logs.log, or empty string if file logging is disabled.
IsPortable() bool true when running in portable mode.
IsFirstRun() bool true on the first launch when no settings file exists yet.

Thread safety

The returned const char* strings are valid until the next call on the same thread. Copy them if you need to pass them to a background thread.

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.

cpp
addon.RegisterString("version", "1.2.0");
addon.RegisterString("author", "Novadesk Team");

RegisterNumber(name, value) ​

Registers a numeric property (supports double, int, float).

cpp
addon.RegisterNumber("maxConnections", 100);
addon.RegisterNumber("pi", 3.14159);

RegisterBool(name, value) ​

Registers a boolean property.

cpp
addon.RegisterBool("isEnabled", true);

RegisterArray(name, values) ​

Registers an array of either strings or numbers.

cpp
// 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.

cpp
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).

cpp
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.

cpp
host->PushString(ctx, "Directly pushed string");

PushNumber(value) ​

Pushes a numeric value.

cpp
host->PushNumber(ctx, 3.14);

PushBool(value) ​

Pushes a boolean value (as 1 or 0).

cpp
host->PushBool(ctx, 1); // true

PushNull() ​

Pushes a null value.

cpp
host->PushNull(ctx);

PushObject() ​

Pushes a new, empty JavaScript object.

cpp
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).

cpp
// 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.

cpp
// 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.

cpp
// 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);

GetProperty(objIndex, name) ​

Reads a property from a JavaScript object at objIndex and pushes the value onto the stack. Returns true if the property exists (not undefined).

cpp
// JS: addon.describe({ title: "Hello", count: 2 })
addon.RegisterFunction("describe", [host](novadesk_context ctx) -> int {
    novadesk::Addon helper(ctx, host);
    if (!helper.IsObject(0)) {
        helper.ThrowError("describe() expects an object");
        return 0;
    }

    if (helper.GetProperty(0, "title")) {
        const char* title = helper.GetString(-1);
        helper.Pop();
        host->PushString(ctx, title);
        return 1;
    }

    helper.ThrowError("Missing title");
    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.

cpp
int count = helper.GetTop();

Pop() ​

Removes the top-most item from the stack.

cpp
helper.Pop();

PopN(n) ​

Removes n items from the stack.

cpp
helper.PopN(3); // Remove top 3 items

ThrowError(message) ​

Improves error reporting by throwing a native JavaScript error. This immediately interrupts C++ execution of the function.

cpp
if (idx < 0) {
    helper.ThrowError("Index cannot be negative!");
    return 0;
}

Example: Robust Math ​

cpp
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.

cpp
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.

cpp
// 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:

cpp
host->RegisterObjectStart(ctx, "myObject");
host->RegisterString(ctx, "innerProp", "value");
host->RegisterObjectEnd(ctx, "myObject");

Object Property Read (Raw) ​

To read a property from an object argument and leave it on the stack:

cpp
// obj is at index 0
if (host->GetProperty(ctx, 0, "name")) {
    const char* name = host->GetString(ctx, -1);
    host->Pop(ctx);
}

Array Registration (Raw) ​

To register arrays using raw pointers:

cpp
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:

cpp
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 ​

cpp
#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);
}