2026-01-26 04:12:14 +00:00
{# retoor < retoor @ molodetz . nl > #}
{% extends 'page.html' %}
2026-01-25 18:02:02 +00:00
2026-01-26 04:12:14 +00:00
{% set page_title = "C-Backed Modules" %}
{% set breadcrumb = [{"url": "contributing/index.html", "title": "Contributing"}, {"title": "C-Backed Modules"}] %}
{% set prev_page = {"url": "contributing/pure-wren-module.html", "title": "Pure-Wren Modules"} %}
{% set next_page = {"url": "contributing/foreign-classes.html", "title": "Foreign Classes"} %}
{% block article %}
2026-01-25 18:02:02 +00:00
< h1 > C-Backed Modules</ h1 >
< p > C-backed modules implement foreign methods in C, providing access to system libraries, native performance, or functionality not available in pure Wren. Examples: json, io, net, crypto, tls, sqlite, base64.</ p >
< h2 > Step 1: Create the Wren Interface</ h2 >
< p > Create < code > src/module/< name> .wren</ code > with foreign method declarations:</ p >
< pre >< code > // retoor < retoor@molodetz.nl>
class Counter {
foreign static create()
foreign static increment(handle)
foreign static getValue(handle)
foreign static destroy(handle)
static use(fn) {
var handle = Counter.create()
var result = fn.call(handle)
Counter.destroy(handle)
return result
}
}</ code ></ pre >
< p > The < code > foreign</ code > keyword indicates the method is implemented in C. Non-foreign methods can provide higher-level Wren wrappers around the foreign primitives.</ p >
< h2 > Step 2: Generate the .wren.inc</ h2 >
< pre >< code > python3 util/wren_to_c_string.py src/module/counter.wren.inc src/module/counter.wren</ code ></ pre >
< h2 > Step 3: Create the C Implementation</ h2 >
< p > Create < code > src/module/counter.c</ code > :</ p >
< pre >< code > // retoor < retoor@molodetz.nl>
#include < stdlib.h>
#include "counter.h"
#include "wren.h"
typedef struct {
int value;
} Counter;
void counterCreate(WrenVM* vm) {
Counter* counter = (Counter*)malloc(sizeof(Counter));
if (!counter) {
wrenSetSlotNull(vm, 0);
return;
}
counter->value = 0;
wrenSetSlotDouble(vm, 0, (double)(uintptr_t)counter);
}
void counterIncrement(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
counter->value++;
}
void counterGetValue(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
wrenSetSlotDouble(vm, 0, counter->value);
}
void counterDestroy(WrenVM* vm) {
double handle = wrenGetSlotDouble(vm, 1);
Counter* counter = (Counter*)(uintptr_t)handle;
free(counter);
}</ code ></ pre >
< h2 > Step 4: Create the C Header</ h2 >
< p > Create < code > src/module/counter.h</ code > :</ p >
< pre >< code > // retoor < retoor@molodetz.nl>
#ifndef counter_h
#define counter_h
#include "wren.h"
void counterCreate(WrenVM* vm);
void counterIncrement(WrenVM* vm);
void counterGetValue(WrenVM* vm);
void counterDestroy(WrenVM* vm);
#endif</ code ></ pre >
< h2 > Step 5: Register in modules.c</ h2 >
< p > Edit < code > src/cli/modules.c</ code > :</ p >
< h3 > Add the Include</ h3 >
< pre >< code > #include "counter.wren.inc"</ code ></ pre >
< h3 > Add Extern Declarations</ h3 >
< pre >< code > extern void counterCreate(WrenVM* vm);
extern void counterIncrement(WrenVM* vm);
extern void counterGetValue(WrenVM* vm);
extern void counterDestroy(WrenVM* vm);</ code ></ pre >
< h3 > Add the Module Entry</ h3 >
< pre >< code > MODULE(counter)
CLASS(Counter)
STATIC_METHOD("create()", counterCreate)
STATIC_METHOD("increment(_)", counterIncrement)
STATIC_METHOD("getValue(_)", counterGetValue)
STATIC_METHOD("destroy(_)", counterDestroy)
END_CLASS
END_MODULE</ code ></ pre >
< h2 > Step 6: Update the Makefile</ h2 >
< p > Edit < code > projects/make/wren_cli.make</ code > :</ p >
< h3 > Add to OBJECTS</ h3 >
< pre >< code > OBJECTS += $(OBJDIR)/counter.o</ code ></ pre >
< h3 > Add Compilation Rule</ h3 >
< pre >< code > $(OBJDIR)/counter.o: ../../src/module/counter.c
@echo $(notdir $< )
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$< "</ code ></ pre >
< h2 > Step 7: Build and Test</ h2 >
< pre >< code > make clean && make build
python3 util/test.py counter</ code ></ pre >
< h2 > Wren/C Data Exchange</ h2 >
< h3 > Getting Values from Wren</ h3 >
< pre >< code > const char* str = wrenGetSlotString(vm, 1);
double num = wrenGetSlotDouble(vm, 1);
bool b = wrenGetSlotBool(vm, 1);
void* foreign = wrenGetSlotForeign(vm, 0);
WrenHandle* handle = wrenGetSlotHandle(vm, 1);
int count = wrenGetSlotCount(vm);
WrenType type = wrenGetSlotType(vm, 1);</ code ></ pre >
< h3 > Setting Return Values</ h3 >
< pre >< code > wrenSetSlotString(vm, 0, "result");
wrenSetSlotDouble(vm, 0, 42.0);
wrenSetSlotBool(vm, 0, true);
wrenSetSlotNull(vm, 0);
wrenSetSlotNewList(vm, 0);</ code ></ pre >
< h3 > Working with Lists</ h3 >
< pre >< code > wrenSetSlotNewList(vm, 0);
wrenSetSlotString(vm, 1, "item");
wrenInsertInList(vm, 0, -1, 1);
int count = wrenGetListCount(vm, 0);
wrenGetListElement(vm, 0, index, 1);</ code ></ pre >
< h3 > Working with Maps</ h3 >
< pre >< code > wrenSetSlotNewMap(vm, 0);
wrenSetSlotString(vm, 1, "key");
wrenSetSlotDouble(vm, 2, 123);
wrenSetMapValue(vm, 0, 1, 2);</ code ></ pre >
< h3 > Ensuring Slots</ h3 >
< pre >< code > wrenEnsureSlots(vm, 5);</ code ></ pre >
< div class = "admonition note" >
< div class = "admonition-title" > Note</ div >
< p > Slot 0 is used for the return value and (for instance methods) the receiver. Arguments start at slot 1.</ p >
</ div >
< h2 > Error Handling</ h2 >
< p > Use < code > wrenAbortFiber</ code > to report errors:</ p >
< pre >< code > void myMethod(WrenVM* vm) {
const char* path = wrenGetSlotString(vm, 1);
FILE* file = fopen(path, "r");
if (!file) {
wrenSetSlotString(vm, 0, "Failed to open file.");
wrenAbortFiber(vm, 0);
return;
}
// ... process file ...
}</ code ></ pre >
< p > The error message is set in slot 0, then < code > wrenAbortFiber</ code > is called with the slot containing the message.</ p >
< h2 > Type Checking</ h2 >
< p > Verify argument types before using them:</ p >
< pre >< code > void myMethod(WrenVM* vm) {
if (wrenGetSlotType(vm, 1) != WREN_TYPE_STRING) {
wrenSetSlotString(vm, 0, "Argument must be a string.");
wrenAbortFiber(vm, 0);
return;
}
const char* str = wrenGetSlotString(vm, 1);
// ...
}</ code ></ pre >
< p > WrenType values: < code > WREN_TYPE_BOOL</ code > , < code > WREN_TYPE_NUM</ code > , < code > WREN_TYPE_FOREIGN</ code > , < code > WREN_TYPE_LIST</ code > , < code > WREN_TYPE_MAP</ code > , < code > WREN_TYPE_NULL</ code > , < code > WREN_TYPE_STRING</ code > , < code > WREN_TYPE_UNKNOWN</ code > .</ p >
< h2 > Method Signature Rules</ h2 >
< table >
< tr >
< th > Wren Declaration</ th >
< th > Signature String</ th >
</ tr >
< tr >
< td >< code > foreign static foo()</ code ></ td >
< td >< code > "foo()"</ code ></ td >
</ tr >
< tr >
< td >< code > foreign static foo(a)</ code ></ td >
< td >< code > "foo(_)"</ code ></ td >
</ tr >
< tr >
< td >< code > foreign static foo(a, b)</ code ></ td >
< td >< code > "foo(_,_)"</ code ></ td >
</ tr >
< tr >
< td >< code > foreign foo()</ code ></ td >
< td >< code > "foo()"</ code > with < code > METHOD</ code ></ td >
</ tr >
< tr >
< td >< code > foreign name</ code > (getter)</ td >
< td >< code > "name"</ code ></ td >
</ tr >
< tr >
< td >< code > foreign name=(v)</ code > (setter)</ td >
< td >< code > "name=(_)"</ code ></ td >
</ tr >
</ table >
< h2 > Complete Example</ h2 >
< p > A more realistic example parsing hexadecimal strings:</ p >
< h3 > hex.wren</ h3 >
< pre >< code > // retoor < retoor@molodetz.nl>
class Hex {
foreign static encode(bytes)
foreign static decode(str)
static isValid(str) {
for (c in str) {
var code = c.bytes[0]
var valid = (code >= 48 && code < = 57) ||
(code >= 65 && code < = 70) ||
(code >= 97 && code < = 102)
if (!valid) return false
}
return str.count \% 2 == 0
}
}</ code ></ pre >
< h3 > hex.c</ h3 >
< pre >< code > // retoor < retoor@molodetz.nl>
#include < stdlib.h>
#include < string.h>
#include "hex.h"
#include "wren.h"
static const char HEX_CHARS[] = "0123456789abcdef";
void hexEncode(WrenVM* vm) {
const char* input = wrenGetSlotString(vm, 1);
size_t len = strlen(input);
char* output = (char*)malloc(len * 2 + 1);
if (!output) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
for (size_t i = 0; i < len ; i ++) {
unsigned char c = (unsigned char ) input [ i ];
output [ i * 2 ] = HEX_CHARS [( c > > 4) & 0xF];
output[i * 2 + 1] = HEX_CHARS[c & 0xF];
}
output[len * 2] = '\0';
wrenSetSlotString(vm, 0, output);
free(output);
}
static int hexCharToInt(char c) {
if (c >= '0' && c < = '9') return c - '0';
if (c >= 'A' && c < = 'F') return c - 'A' + 10;
if (c >= 'a' && c < = 'f') return c - 'a' + 10;
return -1;
}
void hexDecode(WrenVM* vm) {
const char* input = wrenGetSlotString(vm, 1);
size_t len = strlen(input);
if (len \% 2 != 0) {
wrenSetSlotString(vm, 0, "Hex string must have even length.");
wrenAbortFiber(vm, 0);
return;
}
char* output = (char*)malloc(len / 2 + 1);
if (!output) {
wrenSetSlotString(vm, 0, "Memory allocation failed.");
wrenAbortFiber(vm, 0);
return;
}
for (size_t i = 0; i < len ; i += 2 ) {
int high = hexCharToInt(input[i]);
int low = hexCharToInt(input[i + 1 ]);
if ( high < 0 || low < 0 ) {
free ( output );
wrenSetSlotString ( vm , 0 , " Invalid hex character .");
wrenAbortFiber ( vm , 0 );
return ;
}
output [ i / 2 ] = ( char )(( high << 4 ) | low );
}
output [ len / 2 ] = '\ 0 ';
wrenSetSlotString ( vm , 0 , output );
free ( output );
}</ code ></ pre >
< h3 > hex.h</ h3 >
< pre >< code > // retoor < retoor@molodetz.nl>
#ifndef hex_h
#define hex_h
#include "wren.h"
void hexEncode(WrenVM* vm);
void hexDecode(WrenVM* vm);
#endif</ code ></ pre >
< h3 > modules.c entries</ h3 >
< pre >< code > #include "hex.wren.inc"
extern void hexEncode(WrenVM* vm);
extern void hexDecode(WrenVM* vm);
MODULE(hex)
CLASS(Hex)
STATIC_METHOD("encode(_)", hexEncode)
STATIC_METHOD("decode(_)", hexDecode)
END_CLASS
END_MODULE</ code ></ pre >
< h2 > Common Pitfalls</ h2 >
< ul >
< li >< strong > Stale .wren.inc</ strong > : Always regenerate after editing the < code > .wren</ code > file</ li >
< li >< strong > Signature mismatch</ strong > : The string in < code > STATIC_METHOD("name(_)", fn)</ code > must exactly match the Wren declaration's arity</ li >
< li >< strong > Slot management</ strong > : Always call < code > wrenEnsureSlots(vm, n)</ code > before using high-numbered slots</ li >
< li >< strong > Memory leaks</ strong > : Free allocated memory before returning</ li >
< li >< strong > String lifetime</ strong > : Strings from < code > wrenGetSlotString</ code > are valid only until the next Wren API call</ li >
< li >< strong > make clean</ strong > : Required after adding new object files</ li >
</ ul >
< h2 > Checklist</ h2 >
< ul >
< li > Created < code > src/module/< name> .wren</ code > with foreign declarations</ li >
< li > Generated < code > .wren.inc</ code ></ li >
< li > Created < code > src/module/< name> .c</ code ></ li >
< li > Created < code > src/module/< name> .h</ code ></ li >
< li > Added < code > #include</ code > for < code > .wren.inc</ code > in modules.c</ li >
< li > Added extern declarations in modules.c</ li >
< li > Added < code > MODULE</ code > /< code > CLASS</ code > /< code > METHOD</ code > block in modules.c</ li >
< li > Added < code > OBJECTS</ code > entry in Makefile</ li >
< li > Added compilation rule in Makefile</ li >
< li > Built with < code > make clean && make build</ code ></ li >
< li > Created tests and example</ li >
< li > Created documentation page</ li >
</ ul >
< h2 > Next Steps</ h2 >
< ul >
< li >< a href = "foreign-classes.html" > Foreign Classes</ a > - for native resource management</ li >
< li >< a href = "async-patterns.html" > Async Patterns</ a > - for non-blocking I/O operations</ li >
</ ul >
2026-01-26 04:12:14 +00:00
{% endblock %}