Writing C Modules
Cell makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
Basic Structure
A C module exports a single function that returns a JavaScript value:
// mymodule.c
#include "cell.h"
#define CELL_USE_NAME js_mypackage_mymodule_use
static JSValue js_add(JSContext *js, JSValue self, int argc, JSValue *argv) {
double a = js2number(js, argv[0]);
double b = js2number(js, argv[1]);
return number2js(js, a + b);
}
static JSValue js_multiply(JSContext *js, JSValue self, int argc, JSValue *argv) {
double a = js2number(js, argv[0]);
double b = js2number(js, argv[1]);
return number2js(js, a * b);
}
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(mymodule, add, 2),
MIST_FUNC_DEF(mymodule, multiply, 2),
};
CELL_USE_FUNCS(js_funcs)
Symbol Naming
The exported function must follow this naming convention:
js_<package>_<filename>_use
Where:
- <package> is the package name with / and . replaced by _
- <filename> is the C file name without extension
Examples:
- mypackage/math.c → js_mypackage_math_use
- gitea.pockle.world/john/lib/render.c → js_gitea_pockle_world_john_lib_render_use
Required Headers
Include cell.h for all Cell integration:
#include "cell.h"
This provides: - QuickJS types and functions - Conversion helpers - Module definition macros
Conversion Functions
JavaScript ↔ C
// Numbers
double js2number(JSContext *js, JSValue v);
JSValue number2js(JSContext *js, double g);
// Booleans
int js2bool(JSContext *js, JSValue v);
JSValue bool2js(JSContext *js, int b);
// Strings (must free with JS_FreeCString)
const char *JS_ToCString(JSContext *js, JSValue v);
void JS_FreeCString(JSContext *js, const char *str);
JSValue JS_NewString(JSContext *js, const char *str);
Blobs
// Get blob data (returns pointer, sets size in bytes)
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v);
// Get blob data in bits
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v);
// Create new stone blob from data
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes);
// Check if value is a blob
int js_is_blob(JSContext *js, JSValue v);
Function Definition Macros
JSC_CCALL
Define a function with automatic return value:
JSC_CCALL(mymodule_greet,
const char *name = JS_ToCString(js, argv[0]);
char buf[256];
snprintf(buf, sizeof(buf), "Hello, %s!", name);
ret = JS_NewString(js, buf);
JS_FreeCString(js, name);
)
JSC_SCALL
Shorthand for functions taking a string first argument:
JSC_SCALL(mymodule_strlen,
ret = number2js(js, strlen(str));
)
MIST_FUNC_DEF
Register a function in the function list:
MIST_FUNC_DEF(prefix, function_name, arg_count)
Module Export Macros
CELL_USE_FUNCS
Export an object with functions:
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(mymod, func1, 1),
MIST_FUNC_DEF(mymod, func2, 2),
};
CELL_USE_FUNCS(js_funcs)
CELL_USE_INIT
For custom initialization:
CELL_USE_INIT(
JSValue obj = JS_NewObject(js);
// Custom setup...
return obj;
)
Complete Example
// vector.c - Simple 2D vector operations
#include "cell.h"
#include <math.h>
#define CELL_USE_NAME js_mypackage_vector_use
JSC_CCALL(vector_length,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
ret = number2js(js, sqrt(x*x + y*y));
)
JSC_CCALL(vector_normalize,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
ret = result;
}
)
JSC_CCALL(vector_dot,
double x1 = js2number(js, argv[0]);
double y1 = js2number(js, argv[1]);
double x2 = js2number(js, argv[2]);
double y2 = js2number(js, argv[3]);
ret = number2js(js, x1*x2 + y1*y2);
)
static const JSCFunctionListEntry js_funcs[] = {
MIST_FUNC_DEF(vector, length, 2),
MIST_FUNC_DEF(vector, normalize, 2),
MIST_FUNC_DEF(vector, dot, 4),
};
CELL_USE_FUNCS(js_funcs)
Usage in Cell:
var vector = use('vector')
var len = vector.length(3, 4) // 5
var n = vector.normalize(3, 4) // {x: 0.6, y: 0.8}
var d = vector.dot(1, 0, 0, 1) // 0
Combining C and Cell
A common pattern is to have a C file provide low-level functions and a .cm file provide a higher-level API:
// _vector_native.c
// ... raw C functions ...
// vector.cm
var native = this // C module passed as 'this'
function Vector(x, y) {
return {x: x, y: y}
}
Vector.length = function(v) {
return native.length(v.x, v.y)
}
Vector.normalize = function(v) {
return native.normalize(v.x, v.y)
}
return Vector
Build Process
C files are automatically compiled when you run:
cell build
cell update
The resulting dynamic library is placed in ~/.cell/lib/.
Platform-Specific Code
Use filename suffixes for platform variants:
audio.c # default
audio_playdate.c # Playdate
audio_emscripten.c # Web/Emscripten
Cell selects the appropriate file based on the target platform.
Static Declarations
Keep internal functions and variables static:
static int helper_function(int x) {
return x * 2;
}
static int module_state = 0;
This prevents symbol conflicts between packages.